waterfurnace_aurora 0.4.5 → 0.5.3

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 792824de511d596a9c0cacc0ef0eae5385e0ec72e4c9da1765292d4825256769
4
- data.tar.gz: 9cbd94692b1074e7430e6089348d07d5a31506f6e679d82aaa8762316efbf634
3
+ metadata.gz: a372b51d3240840ae96269020f73728fb049f3e6c0438613c9659d4158d21aa6
4
+ data.tar.gz: 249e26d5764a0ab670a60b5de7ed6da0796344fd71896c6c33d353fc4a7d95ed
5
5
  SHA512:
6
- metadata.gz: cec2a4f8ef4353a3c824af4d701dc6fe8ccc904d2ead6176b3507828023430ff335a6d2f2a5132e764d21adf8242973677d0b857079c74f16e8b49ae14a45977
7
- data.tar.gz: c52dfdbc4e6e18c636c3e61226b11f7b4cc056fd76413794a41235936783f07ea09f46db77a901c02957a50ba82555cf35e84794c8903d73b00cf2fd830b480b
6
+ metadata.gz: c5c5ef5a5e1d6ec3d393913ca1b717a5a53a5f17c51f2c9b8ea0f6aeb627cdc873fa25e22511a779d7b9c0e9a60fdc21bc63dbb1fb0d8f6baa41c3dada85cb91
7
+ data.tar.gz: ee787175c344db12fe11f0dab4131505e5692ca360adeee78bcd0931bd4b28660de9c0d09817c5f30632808b374a3695f8a8cbf9f1e965bf410648f411ffa958
@@ -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, retain: false, qos: 1)
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, retain: false, qos: 1)
74
+ @homie.mqtt.publish("#{@homie.topic}/$modbus/#{r}", v, qos: 1)
63
75
  end
64
76
  end
65
77
  end
@@ -76,8 +88,11 @@ 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,
95
+ @humidifier => @abc.humidifier }.compact
81
96
  @abc.zones.each_with_index do |z, idx|
82
97
  homie_zone = @homie["zone#{idx + 1}"]
83
98
  components[homie_zone] = z
@@ -89,12 +104,6 @@ class MQTTBridge
89
104
  end
90
105
  end
91
106
 
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
107
  @abc.faults.each_with_index do |fault_count, i|
99
108
  @faults["e#{i + 1}"].value = fault_count
100
109
  end
@@ -109,8 +118,6 @@ class MQTTBridge
109
118
 
110
119
  def publish_basic_attributes
111
120
  @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
121
  node.property("current-mode", "Current Heating/Cooling Mode", :enum, @abc.current_mode,
115
122
  format: %w[lockout standby blower heating cooling eh1 eh2 emergency waiting dehumidify])
116
123
  node.property("entering-air-temperature", "Entering Air Temperature", :float, @abc.entering_air_temperature,
@@ -128,13 +135,29 @@ class MQTTBridge
128
135
  format: 0..100)
129
136
  node.property("fp1", "FP1 Sensor", :float, @abc.fp1, unit: "ºF")
130
137
  node.property("fp2", "FP2 Sensor", :float, @abc.fp2, unit: "ºF")
131
- %i[compressor aux_heat total].each do |component|
138
+ %i[aux_heat total].each do |component|
132
139
  component = "#{component}_watts"
133
140
  node.property(component.tr("_", "-"), component.tr("_", " ").titleize, :integer,
134
141
  @abc.public_send(component), unit: "W")
135
142
  end
136
143
  end
137
144
 
145
+ @compressor = @homie.node("compressor", "Compressor", @abc.compressor.type) do |node|
146
+ node.property("speed", "Current compressor speed", :integer, @abc.compressor.speed,
147
+ format: @abc.compressor.speed_range)
148
+ node.property("watts", "Energy Usage", :integer, @abc.compressor.watts, unit: "W") if @abc.energy_monitoring?
149
+
150
+ next unless @abc.compressor.is_a?(Aurora::Compressor::VSDrive)
151
+
152
+ node.property("ambient-temperature", "Compressor ambient temperature", :float,
153
+ @abc.compressor.ambient_temperature, unit: "ºF")
154
+
155
+ next unless @abc.iz2?
156
+
157
+ node.property("iz2-desired-speed", "IZ2 Desired Speed", :integer, @abc.compressor.iz2_desired_speed,
158
+ format: 0..12)
159
+ end
160
+
138
161
  @blower = @homie.node("blower", "Blower", @abc.blower.type) do |node|
139
162
  if @abc.blower.respond_to?(:running)
140
163
  node.property("running", "Blower is running", :boolean, @abc.blower.running?)
@@ -154,10 +177,11 @@ class MQTTBridge
154
177
  @mutex.synchronize { property.value = @abc.blower.public_send("#{field}=", value) }
155
178
  end
156
179
  end
157
- if @abc.iz2?
158
- node.property("iz2-desired-speed", "IZ2 Desired Speed", :integer, @abc.blower.iz2_desired_speed,
159
- format: 0..100, unit: "%")
160
- end
180
+
181
+ next unless @abc.iz2?
182
+
183
+ node.property("iz2-desired-speed", "IZ2 Desired Speed", :integer, @abc.blower.iz2_desired_speed,
184
+ format: 0..100, unit: "%")
161
185
  end
162
186
 
163
187
  @pump = @homie.node("pump", "Loop Pump", @abc.pump.type) do |node|
@@ -183,21 +207,30 @@ class MQTTBridge
183
207
  end
184
208
  end
185
209
 
186
- if @abc.dhw?
210
+ if @abc.dhw
187
211
  @dhw = @homie.node("dhw", "Domestic Hot Water Generator", "DHW") do |node|
188
- node.property("enabled", "Enabled", :boolean, @abc.dhw_enabled) do |value, property|
189
- @mutex.synchronize { property.value = @abc.dhw_enabled = value }
212
+ node.property("enabled", "Enabled", :boolean, @abc.dhw.enabled) do |value, property|
213
+ @mutex.synchronize { property.value = @abc.dhw.enabled = value }
190
214
  end
191
- node.property("water-temperature", "DHW Water Temperature", :float,
192
- @abc.dhw_water_temperature, unit: "ºF")
193
- node.property("set-point", "DHW Set Point", :float, @abc.dhw_setpoint, format: 100..140,
194
- unit: "ºF") do |value, property|
195
- @mutex.synchronize { property.value = @abc.dhw_setpoint = value }
215
+ node.property("water-temperature", "Water Temperature", :float,
216
+ @abc.dhw.water_temperature, unit: "ºF")
217
+ node.property("set-point", "Set Point", :float, @abc.dhw.set_point, format: 100..140,
218
+ unit: "ºF") do |value, property|
219
+ @mutex.synchronize { property.value = @abc.dhw.set_point = value }
196
220
  end
197
221
  end
198
222
  end
199
223
 
224
+ if @abc.humidifier
225
+ @humidifier = @homie.node("humidifier", "Humidifier", "Humidifier") do |node|
226
+ node.property("running", "Humidifier is running", :boolean, @abc.humidifier.running?)
227
+ end
228
+ end
229
+
200
230
  @faults = @homie.node("faults", "Fault History", "ABC") do |node|
231
+ node.property("clear-history", "Reset fault counts", :enum, retained: false, format: "clear") do |_|
232
+ @mutex.synchronize { @abc.clear_fault_history }
233
+ end
201
234
  @abc.faults.each_with_index do |count, i|
202
235
  name = Aurora::FAULTS[i + 1]
203
236
  node.property("e#{i + 1}", name || "E#{i + 1}", :integer, count)
@@ -213,25 +246,21 @@ class MQTTBridge
213
246
  format: allowed_modes) do |value, property|
214
247
  @mutex.synchronize { property.value = zone.target_mode = value.to_sym }
215
248
  end
216
- if zone.respond_to?(:current_mode) # TODO: implement for non-IZ2
217
- node.property("current-mode", "Current Heating/Cooling Mode Requested", :enum, zone.current_mode,
218
- format: %w[standby h1 h2 h3 c1 c2])
219
- end
249
+ node.property("current-mode", "Current Heating/Cooling Mode Requested", :enum, zone.current_mode,
250
+ format: %w[standby h1 h2 h3 c1 c2])
220
251
  node.property("target-fan-mode", "Target Fan Mode", :enum, zone.target_fan_mode,
221
252
  format: %w[auto continuous intermittent]) do |value, property|
222
253
  @mutex.synchronize { property.value = zone.target_fan_mode = value.to_sym }
223
254
  end
224
- if zone.respond_to?(:current_fan_mode) # TODO: implement for non-IZ2
225
- node.property("current-fan-mode", "Current Fan Status", :boolean, zone.current_fan_mode)
226
- node.property("fan-intermittent-on", "Fan Intermittent Mode On Duration", :enum, zone.fan_intermittent_on,
227
- unit: "M", format: %w[0 5 10 15 20]) do |value, property|
228
- @mutex.synchronize { property.value = zone.fan_intermittent_on = value.to_i }
229
- end
230
- node.property("fan-intermittent-off", "Fan Intermittent Mode Off Duration", :enum, zone.fan_intermittent_on,
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
255
+ node.property("fan-intermittent-on", "Fan Intermittent Mode On Duration", :enum, zone.fan_intermittent_on,
256
+ unit: "M", format: %w[0 5 10 15 20]) do |value, property|
257
+ @mutex.synchronize { property.value = zone.fan_intermittent_on = value.to_i }
258
+ end
259
+ node.property("fan-intermittent-off", "Fan Intermittent Mode Off Duration", :enum, zone.fan_intermittent_on,
260
+ unit: "M", format: %w[0 5 10 15 20 25 30 35 40]) do |value, property|
261
+ @mutex.synchronize { property.value = zone.fan_intermittent_on = value.to_i }
234
262
  end
263
+ node.property("current-fan-mode", "Current Fan Status", :boolean, zone.current_fan_mode)
235
264
  if zone.is_a?(Aurora::IZ2Zone)
236
265
  node.property("priority", "Zone Priority", :enum, zone.priority, format: %w[economy comfort])
237
266
  node.property("size", "Size", :enum, zone.size, format: %w[0 25 45 70])
@@ -251,6 +280,7 @@ class MQTTBridge
251
280
 
252
281
  # direct access to modbus registers for debugging purposes
253
282
  @homie.mqtt.subscribe("#{@homie.topic}/$modbus")
283
+ @homie.mqtt.subscribe("#{@homie.topic}/$modbus/getregs")
254
284
  @homie.mqtt.subscribe("#{@homie.topic}/$modbus/+/set")
255
285
  @homie.publish
256
286
  end
@@ -4,6 +4,9 @@ require "yaml"
4
4
  require "uri"
5
5
 
6
6
  require "aurora/blower"
7
+ require "aurora/compressor"
8
+ require "aurora/dhw"
9
+ require "aurora/humidifier"
7
10
  require "aurora/iz2_zone"
8
11
  require "aurora/pump"
9
12
  require "aurora/thermostat"
@@ -24,7 +27,9 @@ module Aurora
24
27
  port: uri.port || 23,
25
28
  baud: 19_200,
26
29
  parity: :even)
27
-
30
+ when "mqtt", "mqtts"
31
+ require "aurora/mqtt_modbus"
32
+ return Aurora::MQTTModBus.new(uri)
28
33
  else
29
34
  return Aurora::MockABC.new(YAML.load_file(uri.path)) if File.file?(uri.path)
30
35
 
@@ -40,23 +45,21 @@ module Aurora
40
45
  attr_reader :modbus_slave,
41
46
  :serial_number,
42
47
  :zones,
48
+ :compressor,
43
49
  :blower,
44
50
  :pump,
51
+ :dhw,
52
+ :humidifier,
45
53
  :faults,
46
54
  :current_mode,
47
- :dhw_enabled,
48
- :dhw_setpoint,
49
55
  :entering_air_temperature,
50
56
  :relative_humidity,
51
57
  :leaving_air_temperature,
52
58
  :leaving_water_temperature,
53
59
  :entering_water_temperature,
54
- :dhw_water_temperature,
55
- :compressor_speed,
56
60
  :outdoor_temperature,
57
61
  :fp1,
58
62
  :fp2,
59
- :compressor_watts,
60
63
  :aux_heat_watts,
61
64
  :total_watts
62
65
 
@@ -64,13 +67,26 @@ module Aurora
64
67
  @modbus_slave = self.class.open_modbus_slave(uri)
65
68
  @modbus_slave.read_retry_timeout = 15
66
69
  @modbus_slave.read_retries = 2
67
- raw_registers = @modbus_slave.holding_registers[88..91, 105...110, 404, 412..413, 1114]
70
+ raw_registers = @modbus_slave.holding_registers[33, 88..91, 105...110, 404, 412..413, 1114]
68
71
  registers = Aurora.transform_registers(raw_registers.dup)
69
72
  @program = registers[88]
70
73
  @serial_number = registers[105]
71
- @dhw_water_temperature = registers[1114]
72
74
  @energy_monitor = raw_registers[412]
73
75
 
76
+ @zones = if iz2?
77
+ iz2_zone_count = @modbus_slave.holding_registers[483]
78
+ (0...iz2_zone_count).map { |i| IZ2Zone.new(self, i + 1) }
79
+ else
80
+ [Thermostat.new(self)]
81
+ end
82
+
83
+ @abc_dipswitches = registers[33]
84
+ @compressor = if @program == "ABCVSP"
85
+ Compressor::VSDrive.new(self)
86
+ else
87
+ Compressor::GenericCompressor.new(self,
88
+ @abc_dipswitches[:compressor])
89
+ end
74
90
  @blower = case raw_registers[404]
75
91
  when 1, 2 then Blower::ECM.new(self, registers[404])
76
92
  when 3 then Blower::FiveSpeed.new(self, registers[404])
@@ -83,13 +99,9 @@ module Aurora
83
99
  Pump::GenericPump.new(self,
84
100
  registers[413])
85
101
  end
102
+ @dhw = DHW.new(self) if (-999..999).include?(registers[1114])
103
+ @humidifier = Humidifier.new(self) if @abc_dipswitches[:accessory_relay] == :humidifier
86
104
 
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
105
  @faults = []
94
106
  end
95
107
 
@@ -128,51 +140,32 @@ module Aurora
128
140
  end
129
141
 
130
142
  def refresh
131
- registers_to_read = [6, 19..20, 25, 30, 344, 740..741, 900, 1110..1111, 1114, 1147..1153, 1165,
143
+ registers_to_read = [6, 19..20, 25, 30, 344, 740..741, 900, 1110..1111, 1114, 1150..1153, 1165,
132
144
  31_003]
133
- registers_to_read << (400..401) if dhw?
145
+ zones.each do |z|
146
+ registers_to_read.concat(z.registers_to_read)
147
+ end
148
+ registers_to_read.concat(compressor.registers_to_read)
134
149
  registers_to_read.concat(blower.registers_to_read)
135
150
  registers_to_read.concat(pump.registers_to_read)
136
- registers_to_read.concat([362, 3001]) if vs_drive?
137
-
138
- if zones.first.is_a?(IZ2Zone)
139
- zones.each_with_index do |_z, i|
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
151
+ registers_to_read.concat(dhw.registers_to_read) if dhw
152
+ # need dehumidify mode to calculate final current mode;
153
+ # apparently non-VSD doesn't have this register at all?
154
+ registers_to_read.concat([362]) if compressor.is_a?(Compressor::VSDrive)
151
155
 
152
- @faults = @modbus_slave.holding_registers[601..699]
156
+ faults = @modbus_slave.read_multiple_holding_registers(601..699)
157
+ @faults = Aurora.transform_registers(faults).values
153
158
 
154
159
  registers = @modbus_slave.holding_registers[*registers_to_read]
155
160
  Aurora.transform_registers(registers)
156
161
 
157
162
  outputs = registers[30]
158
163
 
159
- @dhw_enabled = registers[400]
160
- @dhw_setpoint = registers[401]
161
164
  @entering_air_temperature = registers[740]
162
165
  @relative_humidity = registers[741]
163
166
  @leaving_air_temperature = registers[900]
164
167
  @leaving_water_temperature = registers[1110]
165
168
  @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
169
  @outdoor_temperature = registers[31_003]
177
170
  @fp1 = registers[19]
178
171
  @fp2 = registers[20]
@@ -180,7 +173,6 @@ module Aurora
180
173
  @error = registers[25] & 0x7fff
181
174
  @derated = (41..46).include?(@error)
182
175
  @safe_mode = [47, 48, 49, 72, 74].include?(@error)
183
- @compressor_watts = registers[1147]
184
176
  @aux_heat_watts = registers[1151]
185
177
  @total_watts = registers[1153]
186
178
 
@@ -202,12 +194,14 @@ module Aurora
202
194
  :standby
203
195
  end
204
196
 
205
- blower.refresh(registers)
206
- pump.refresh(registers)
207
-
208
197
  zones.each do |z|
209
198
  z.refresh(registers)
210
199
  end
200
+ compressor.refresh(registers)
201
+ blower.refresh(registers)
202
+ pump.refresh(registers)
203
+ dhw&.refresh(registers)
204
+ humidifier&.refresh(registers)
211
205
  end
212
206
 
213
207
  def cooling_airflow_adjustment=(value)
@@ -215,16 +209,6 @@ module Aurora
215
209
  @modbus_slave.holding_registers[346] = value
216
210
  end
217
211
 
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
212
  def loop_pressure_trip=(value)
229
213
  @modbus_slave.holding_registers[419] = (value * 10).to_i
230
214
  end
@@ -270,14 +254,6 @@ module Aurora
270
254
  @energy_monitor == 2
271
255
  end
272
256
 
273
- def vs_drive?
274
- @program == "ABCVSP"
275
- end
276
-
277
- def dhw?
278
- (-999..999).include?(dhw_water_temperature)
279
- end
280
-
281
257
  # config aurora system
282
258
  { thermostat: 800, axb: 806, iz2: 812, aoc: 815, moc: 818, eev2: 824 }.each do |(component, register)|
283
259
  class_eval <<-RUBY, __FILE__, __LINE__ + 1
@@ -6,6 +6,12 @@ module Aurora
6
6
  @abc = abc
7
7
  end
8
8
 
9
+ def inspect
10
+ "#<Aurora::#{self.class.name} #{(instance_variables - [:@abc]).map do |iv|
11
+ "#{iv}=#{instance_variable_get(iv).inspect}"
12
+ end.join(', ')}>"
13
+ end
14
+
9
15
  private
10
16
 
11
17
  attr_reader :abc
@@ -0,0 +1,70 @@
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 initialize(abc, stages)
11
+ super(abc)
12
+ @stages = stages
13
+ end
14
+
15
+ def type
16
+ "#{@stages == 2 ? 'Dual' : 'Single'} Stage Compressor"
17
+ end
18
+
19
+ def speed_range
20
+ 0..@stages
21
+ end
22
+
23
+ def registers_to_read
24
+ if abc.energy_monitoring?
25
+ [1146..1147]
26
+ else
27
+ []
28
+ end
29
+ end
30
+
31
+ def refresh(registers)
32
+ outputs = registers[30]
33
+ @speed = if outputs.include?(:cc2)
34
+ 2
35
+ elsif outputs.include?(:cc)
36
+ 1
37
+ else
38
+ 0
39
+ end
40
+ @watts = registers[1146] if abc.energy_monitoring?
41
+ end
42
+ end
43
+
44
+ class VSDrive < GenericCompressor
45
+ attr_reader :ambient_temperature, :iz2_desired_speed
46
+
47
+ def initialize(abc)
48
+ super(abc, 12)
49
+ end
50
+
51
+ def type
52
+ "Variable Speed Drive"
53
+ end
54
+
55
+ def registers_to_read
56
+ result = super + [209, 3001, 3326]
57
+ result << 564 if abc.iz2?
58
+ result
59
+ end
60
+
61
+ def refresh(registers)
62
+ super
63
+
64
+ @speed = registers[3001]
65
+ @ambient_temperature = registers[3326]
66
+ @iz2_desired_speed = registers[564] if abc.iz2?
67
+ end
68
+ end
69
+ end
70
+ 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
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "aurora/component"
4
+
5
+ module Aurora
6
+ class Humidifier < Component
7
+ attr_reader :running
8
+ alias running? running
9
+
10
+ def refresh(registers)
11
+ outputs = registers[30]
12
+ @running = outputs.include?(:accessory)
13
+ end
14
+ end
15
+ end
@@ -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, :normalized_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
 
@@ -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
@@ -194,24 +194,24 @@ module Aurora
194
194
  name ? "#{value} #{name}" : value.to_s
195
195
  end
196
196
 
197
- AR_SETTINGS = {
198
- 0 => "Cycle with Compressor",
199
- 1 => "Slow Opening Water Valve",
200
- 2 => "Cycle with Thermostat Humidification Call",
201
- 3 => "Cycle with Blower"
197
+ ACCESSORY_RELAY_SETTINGS = {
198
+ 0 => :compressor,
199
+ 1 => :slow_opening_water_valve,
200
+ 2 => :humidifier,
201
+ 3 => :blower
202
202
  }.freeze
203
203
 
204
204
  def dipswitch_settings(value)
205
205
  return :manual if value == 0x7fff
206
206
 
207
207
  {
208
- fp1: value & 0x01 == 0x01 ? "30ºF" : "15ºF",
209
- fp2: value & 0x02 == 0x02 ? "30ºF" : "15ºF",
210
- rv: value & 0x04 == 0x04 ? "O" : "B",
211
- ar: AR_SETTINGS[(value >> 3) & 0x3],
212
- cc: value & 0x20 == 0x20 ? "Single Stage" : "Dual Stage",
213
- lo: value & 0x40 == 0x40 ? "Continouous" : "Pulse",
214
- dh_rh: value & 0x80 == 0x80 ? "Dehumidifier On" : "Reheat On"
208
+ fp1: value & 0x01 == 0x01 ? 30 : 15,
209
+ fp2: value & 0x02 == 0x02 ? 30 : :off,
210
+ reversing_valve: value & 0x04 == 0x04 ? :o : :b, # cycle to cool on O, or !B
211
+ accessory_relay: ACCESSORY_RELAY_SETTINGS[(value >> 3) & 0x3],
212
+ compressor: value & 0x20 == 0x20 ? 1 : 2, # single or dual stage compressor
213
+ lockout: value & 0x40 == 0x40 ? :continuous : :pulse,
214
+ dehumidifier_reheat: value & 0x80 == 0x80 ? :dehumidifier : :reheat
215
215
  }
216
216
  end
217
217
 
@@ -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 & 0o1
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?
@@ -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, 1146],
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],
@@ -876,10 +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",
879
892
  12_606 => "Heating Mode (write)",
880
893
  12_619 => "Heating Setpoint (write)",
881
894
  12_620 => "Cooling Setpoint (write)",
882
895
  12_621 => "Fan Mode (write)",
896
+ 12_622 => "Intermittent Fan On Time (write)",
897
+ 12_623 => "Intermittent Fan Off Time (write)",
883
898
  31_003 => "Outdoor Temp",
884
899
  31_005 => "IZ2 Demand",
885
900
  31_109 => "Humidifier Mode", # write to 21114
@@ -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 inspect
50
- "#<Aurora::#{self.class.name} #{(instance_variables - [:@abc]).map do |iv|
51
- "#{iv}=#{instance_variable_get(iv).inspect}"
52
- end.join(', ')}>"
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Aurora
4
- VERSION = "0.4.5"
4
+ VERSION = "0.5.3"
5
5
  end
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.5
4
+ version: 0.5.3
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-09-01 00:00:00.000000000 Z
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,15 @@ 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
142
+ - lib/aurora/humidifier.rb
140
143
  - lib/aurora/iz2_zone.rb
141
144
  - lib/aurora/mock_abc.rb
142
145
  - lib/aurora/modbus/server.rb
143
146
  - lib/aurora/modbus/slave.rb
147
+ - lib/aurora/mqtt_modbus.rb
144
148
  - lib/aurora/pump.rb
145
149
  - lib/aurora/registers.rb
146
150
  - lib/aurora/thermostat.rb