waterfurnace_aurora 0.4.2 → 0.5.0

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: 0e43967c61aa8b5c445ec2a6024e9da5ba8cc94205cc853ff07fed0374827dc3
4
- data.tar.gz: 875004f5c9835f25f7c18d7e7e7d632f0877db6ac7a3fc8215fa28b68c5ce2b9
3
+ metadata.gz: 16b9a4ef085dc672bf6b360947c70a959f03b3406f0c2dae4eac614a2f12c412
4
+ data.tar.gz: 5a7ef967170bd5440b94407e05cfe6e2f1bae40d838a23de26d442a1e4fa9a14
5
5
  SHA512:
6
- metadata.gz: f5d48422906f8dbac8aa83645ab3f46a9481f96d7e4720b75772dfbf2be2cf8456753f5d364a4cde3ad69fb778fd3ba29f23249ec08010806339039264bcbefc
7
- data.tar.gz: 3a78e33d93f256e0153ae98b7f8df92da2f3138c246e1b5ef47550f4759d5502725a1d52acb6fe3f084b8a158962538ed73a3c1acf072cc151bf18ad9db2f75d
6
+ metadata.gz: d31c1d4a82dc9420adedd8b5043ebde59845eb5cc90c14109ad51d533687e0fa7010e91c04858773d9f40523f534118d72f891fb673344810244e2d121c0b764
7
+ data.tar.gz: b83b92e7b7ad9ecb2dda5eae57d2d460a82285420e169acc317c54390234cc123bead659fc56e2be53dbb602dd24e0b4cce66bd4223bd52badd7d543d641a4a7
data/exe/aurora_monitor CHANGED
@@ -30,7 +30,7 @@ end
30
30
 
31
31
  options.parse!
32
32
 
33
- unless ARGV.length == 2
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, 1134, 1147, 1149, 1151,
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 == 68
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}"
@@ -74,30 +74,26 @@ class MQTTBridge
74
74
  begin
75
75
  @mutex.synchronize do
76
76
  @abc.refresh
77
- @homie_abc.each do |property|
78
- property.value = @abc.public_send(property.id.tr("-", "_"))
79
- end
80
77
 
81
- @blower.each do |property|
82
- property.value = @abc.blower.public_send(property.id.tr("-", "_"))
78
+ components = { @homie_abc => @abc,
79
+ @compressor => @abc.compressor,
80
+ @blower => @abc.blower,
81
+ @pump => @abc.pump,
82
+ @dhw => @abc.dhw }.compact
83
+ @abc.zones.each_with_index do |z, idx|
84
+ homie_zone = @homie["zone#{idx + 1}"]
85
+ components[homie_zone] = z
83
86
  end
84
87
 
85
- if @abc.dhw?
86
- @dhw["enabled"].value = @abc.dhw_enabled
87
- @dhw["water-temperature"].value = @abc.dhw_water_temperature
88
- @dhw["set-point"].value = @abc.dhw_setpoint
88
+ components.each do |(node, object)|
89
+ node.each do |property|
90
+ property.value = object.public_send(property.id.tr("-", "_"))
91
+ end
89
92
  end
90
93
 
91
94
  @abc.faults.each_with_index do |fault_count, i|
92
95
  @faults["e#{i + 1}"].value = fault_count
93
96
  end
94
-
95
- @abc.zones.each_with_index do |z, idx|
96
- homie_zone = @homie["zone#{idx + 1}"]
97
- homie_zone.each do |property|
98
- property.value = z.public_send(property.id.tr("-", "_"))
99
- end
100
- end
101
97
  end
102
98
  rescue => e
103
99
  warn "got garbage: #{e}; #{e.backtrace}"
@@ -109,8 +105,6 @@ class MQTTBridge
109
105
 
110
106
  def publish_basic_attributes
111
107
  @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
108
  node.property("current-mode", "Current Heating/Cooling Mode", :enum, @abc.current_mode,
115
109
  format: %w[lockout standby blower heating cooling eh1 eh2 emergency waiting dehumidify])
116
110
  node.property("entering-air-temperature", "Entering Air Temperature", :float, @abc.entering_air_temperature,
@@ -126,16 +120,31 @@ class MQTTBridge
126
120
  end
127
121
  node.property("relative-humidity", "Relative Humidity", :integer, @abc.relative_humidity, unit: "%",
128
122
  format: 0..100)
129
- node.property("waterflow", "Waterflow", :float, @abc.waterflow, unit: "gpm")
130
123
  node.property("fp1", "FP1 Sensor", :float, @abc.fp1, unit: "ºF")
131
124
  node.property("fp2", "FP2 Sensor", :float, @abc.fp2, unit: "ºF")
132
- %i[compressor aux_heat loop_pump total].each do |component|
125
+ %i[aux_heat total].each do |component|
133
126
  component = "#{component}_watts"
134
127
  node.property(component.tr("_", "-"), component.tr("_", " ").titleize, :integer,
135
128
  @abc.public_send(component), unit: "W")
136
129
  end
137
130
  end
138
131
 
132
+ @compressor = @homie.node("compressor", "Compressor", @abc.compressor.type) do |node|
133
+ node.property("speed", "Current compressor speed", :integer, @abc.compressor.speed,
134
+ format: @abc.compressor.speed_range)
135
+ node.property("watts", "Energy Usage", :integer, @abc.compressor.watts, unit: "W") if @abc.energy_monitoring?
136
+
137
+ next unless @abc.compressor.is_a?(Aurora::Compressor::VSDrive)
138
+
139
+ node.property("ambient-temperature", "Compressor ambient temperature", :float,
140
+ @abc.compressor.ambient_temperature, unit: "ºF")
141
+
142
+ next unless @abc.iz2?
143
+
144
+ node.property("iz2-desired-speed", "IZ2 Desired Speed", :integer, @abc.compressor.iz2_desired_speed,
145
+ format: 0..12)
146
+ end
147
+
139
148
  @blower = @homie.node("blower", "Blower", @abc.blower.type) do |node|
140
149
  if @abc.blower.respond_to?(:running)
141
150
  node.property("running", "Blower is running", :boolean, @abc.blower.running?)
@@ -143,31 +152,58 @@ class MQTTBridge
143
152
  node.property("speed", "Current blower speed", :integer, @abc.blower.speed, format: @abc.blower.speed_range)
144
153
  end
145
154
  node.property("watts", "Energy Usage", :integer, @abc.blower.watts, unit: "W") if @abc.energy_monitoring?
146
- if @abc.blower.is_a?(Aurora::Blower::ECM)
147
- %w[blower-only low-compressor high-compressor aux-heat].each do |setting|
148
- field = "#{setting.tr('-', '_')}_speed"
149
- node.property("#{setting}-speed", "#{setting.tr('-', ' ').titleize} Speed", :integer,
150
- @abc.blower.public_send(field), format: 1..12) do |value, property|
151
- @mutex.synchronize { property.value = @abc.blower.public_send("#{field}=", value) }
152
- end
153
- end
154
- if @abc.iz2?
155
- node.property("iz2-desired-speed", "IZ2 Desired Speed", :integer, @abc.blower.iz2_desired_speed,
156
- format: 0..100, unit: "%")
155
+
156
+ next unless @abc.blower.is_a?(Aurora::Blower::ECM)
157
+
158
+ presets = %w[blower-only aux-heat]
159
+ presets.concat %w[low-compressor high-compressor] unless @abc.iz2?
160
+ presets.each do |setting|
161
+ field = "#{setting.tr('-', '_')}_speed"
162
+ node.property("#{setting}-speed", "#{setting.tr('-', ' ').titleize} Preset Speed", :integer,
163
+ @abc.blower.public_send(field), format: 1..12) do |value, property|
164
+ @mutex.synchronize { property.value = @abc.blower.public_send("#{field}=", value) }
157
165
  end
158
166
  end
167
+
168
+ next unless @abc.iz2?
169
+
170
+ node.property("iz2-desired-speed", "IZ2 Desired Speed", :integer, @abc.blower.iz2_desired_speed,
171
+ format: 0..100, unit: "%")
172
+ end
173
+
174
+ @pump = @homie.node("pump", "Loop Pump", @abc.pump.type) do |node|
175
+ node.property("waterflow", "Waterflow", :float, @abc.pump.waterflow, unit: "gpm")
176
+ node.property("watts", "Energy Usage", :integer, @abc.pump.watts, unit: "W") if @abc.energy_monitoring?
177
+
178
+ next unless @abc.pump.is_a?(Aurora::Pump::VSPump)
179
+
180
+ node.property("speed", "Speed", :integer, @abc.pump.speed, format: 0..100, unit: "%") do |value, property|
181
+ @mutex.synchronize { property.value = @abc.pump.speed = value }
182
+ end
183
+ node.property("manual-control", "If manual control is enabled", :boolean,
184
+ @abc.pump.manual_control?) do |value, property|
185
+ @mutex.synchronize { property.value = @abc.pump.manual_control = value }
186
+ end
187
+ node.property("minimum-speed", "Speed pump runs at compressor speed 0", :integer, @abc.pump.minimum_speed,
188
+ format: 0..100, unit: "%") do |value, property|
189
+ @mutex.synchronize { property.value = @abc.pump.minimum_speed = value }
190
+ end
191
+ node.property("maximum-speed", "Speed pump runs at compressor speed 12", :integer, @abc.pump.minimum_speed,
192
+ format: 0..100, unit: "%") do |value, property|
193
+ @mutex.synchronize { property.value = @abc.pump.maximum_speed = value }
194
+ end
159
195
  end
160
196
 
161
- if @abc.dhw?
197
+ if @abc.dhw
162
198
  @dhw = @homie.node("dhw", "Domestic Hot Water Generator", "DHW") do |node|
163
- node.property("enabled", "Enabled", :boolean, @abc.dhw_enabled) do |value, property|
164
- @mutex.synchronize { property.value = @abc.dhw_enabled = value }
199
+ node.property("enabled", "Enabled", :boolean, @abc.dhw.enabled) do |value, property|
200
+ @mutex.synchronize { property.value = @abc.dhw.enabled = value }
165
201
  end
166
- node.property("water-temperature", "DHW Water Temperature", :float,
167
- @abc.dhw_water_temperature, unit: "ºF")
168
- node.property("set-point", "DHW Set Point", :float, @abc.dhw_setpoint, format: 100..140,
169
- unit: "ºF") do |value, property|
170
- @mutex.synchronize { property.value = @abc.dhw_setpoint = value }
202
+ node.property("water-temperature", "Water Temperature", :float,
203
+ @abc.dhw.water_temperature, unit: "ºF")
204
+ node.property("set-point", "Set Point", :float, @abc.dhw.set_point, format: 100..140,
205
+ unit: "ºF") do |value, property|
206
+ @mutex.synchronize { property.value = @abc.dhw.set_point = value }
171
207
  end
172
208
  end
173
209
  end
@@ -188,25 +224,21 @@ class MQTTBridge
188
224
  format: allowed_modes) do |value, property|
189
225
  @mutex.synchronize { property.value = zone.target_mode = value.to_sym }
190
226
  end
191
- if zone.respond_to?(:current_mode) # TODO: implement for non-IZ2
192
- node.property("current-mode", "Current Heating/Cooling Mode Requested", :enum, zone.current_mode,
193
- format: %w[standby h1 h2 h3 c1 c2])
194
- end
227
+ node.property("current-mode", "Current Heating/Cooling Mode Requested", :enum, zone.current_mode,
228
+ format: %w[standby h1 h2 h3 c1 c2])
195
229
  node.property("target-fan-mode", "Target Fan Mode", :enum, zone.target_fan_mode,
196
230
  format: %w[auto continuous intermittent]) do |value, property|
197
231
  @mutex.synchronize { property.value = zone.target_fan_mode = value.to_sym }
198
232
  end
199
- if zone.respond_to?(:current_fan_mode) # TODO: implement for non-IZ2
200
- node.property("current-fan-mode", "Current Fan Status", :boolean, zone.current_fan_mode)
201
- node.property("fan-intermittent-on", "Fan Intermittent Mode On Duration", :enum, zone.fan_intermittent_on,
202
- unit: "M", format: %w[0 5 10 15 20]) do |value, property|
203
- @mutex.synchronize { property.value = zone.fan_intermittent_on = value.to_i }
204
- end
205
- node.property("fan-intermittent-off", "Fan Intermittent Mode Off Duration", :enum, zone.fan_intermittent_on,
206
- unit: "M", format: %w[0 5 10 15 20 25 30 35 40]) do |value, property|
207
- @mutex.synchronize { property.value = zone.fan_intermittent_on = value.to_i }
208
- end
233
+ node.property("fan-intermittent-on", "Fan Intermittent Mode On Duration", :enum, zone.fan_intermittent_on,
234
+ unit: "M", format: %w[0 5 10 15 20]) do |value, property|
235
+ @mutex.synchronize { property.value = zone.fan_intermittent_on = value.to_i }
236
+ end
237
+ node.property("fan-intermittent-off", "Fan Intermittent Mode Off Duration", :enum, zone.fan_intermittent_on,
238
+ unit: "M", format: %w[0 5 10 15 20 25 30 35 40]) do |value, property|
239
+ @mutex.synchronize { property.value = zone.fan_intermittent_on = value.to_i }
209
240
  end
241
+ node.property("current-fan-mode", "Current Fan Status", :boolean, zone.current_fan_mode)
210
242
  if zone.is_a?(Aurora::IZ2Zone)
211
243
  node.property("priority", "Zone Priority", :enum, zone.priority, format: %w[economy comfort])
212
244
  node.property("size", "Size", :enum, zone.size, format: %w[0 25 45 70])
@@ -4,7 +4,10 @@ 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"
10
+ require "aurora/pump"
8
11
  require "aurora/thermostat"
9
12
 
10
13
  module Aurora
@@ -39,50 +42,55 @@ module Aurora
39
42
  attr_reader :modbus_slave,
40
43
  :serial_number,
41
44
  :zones,
45
+ :compressor,
42
46
  :blower,
47
+ :pump,
48
+ :dhw,
43
49
  :faults,
44
50
  :current_mode,
45
- :dhw_enabled,
46
- :dhw_setpoint,
47
51
  :entering_air_temperature,
48
52
  :relative_humidity,
49
53
  :leaving_air_temperature,
50
54
  :leaving_water_temperature,
51
55
  :entering_water_temperature,
52
- :dhw_water_temperature,
53
- :waterflow,
54
- :compressor_speed,
55
56
  :outdoor_temperature,
56
57
  :fp1,
57
58
  :fp2,
58
- :compressor_watts,
59
59
  :aux_heat_watts,
60
- :loop_pump_watts,
61
60
  :total_watts
62
61
 
63
62
  def initialize(uri)
64
63
  @modbus_slave = self.class.open_modbus_slave(uri)
65
64
  @modbus_slave.read_retry_timeout = 15
66
65
  @modbus_slave.read_retries = 2
67
- raw_registers = @modbus_slave.holding_registers[88..91, 105...110, 404, 412, 1114]
66
+ raw_registers = @modbus_slave.holding_registers[88..91, 105...110, 404, 412..413, 1114]
68
67
  registers = Aurora.transform_registers(raw_registers.dup)
69
68
  @program = registers[88]
70
69
  @serial_number = registers[105]
71
- @dhw_water_temperature = registers[1114]
72
70
  @energy_monitor = raw_registers[412]
73
71
 
74
- @blower = case raw_registers[404]
75
- when 1, 2 then Blower::ECM.new(self, registers[404])
76
- when 3 then Blower::FiveSpeed.new(self, registers[404])
77
- else; Blower::PSC.new(self, registers[404])
78
- end
79
-
80
72
  @zones = if iz2?
81
73
  iz2_zone_count = @modbus_slave.holding_registers[483]
82
74
  (0...iz2_zone_count).map { |i| IZ2Zone.new(self, i + 1) }
83
75
  else
84
76
  [Thermostat.new(self)]
85
77
  end
78
+
79
+ @compressor = @program == "ABCVSP" ? Compressor::VSDrive.new(self) : Compressor::GenericCompressor.new(self)
80
+ @blower = case raw_registers[404]
81
+ when 1, 2 then Blower::ECM.new(self, registers[404])
82
+ when 3 then Blower::FiveSpeed.new(self, registers[404])
83
+ else; Blower::PSC.new(self, registers[404])
84
+ end
85
+ @pump = if (3..5).include?(raw_registers[413])
86
+ Pump::VSPump.new(self,
87
+ registers[413])
88
+ else
89
+ Pump::GenericPump.new(self,
90
+ registers[413])
91
+ end
92
+ @dhw = DHW.new(self) if (-999..999).include?(registers[1114])
93
+
86
94
  @faults = []
87
95
  end
88
96
 
@@ -121,25 +129,18 @@ module Aurora
121
129
  end
122
130
 
123
131
  def refresh
124
- registers_to_read = [6, 19..20, 25, 30, 344, 740..741, 900, 1110..1111, 1114, 1117, 1147..1153, 1165,
132
+ registers_to_read = [6, 19..20, 25, 30, 344, 740..741, 900, 1110..1111, 1114, 1150..1153, 1165,
125
133
  31_003]
126
- registers_to_read << (400..401) if dhw?
127
- registers_to_read.concat(blower.registers_to_read)
128
- registers_to_read.concat([362, 3001]) if vs_drive?
129
-
130
- if zones.first.is_a?(IZ2Zone)
131
- zones.each_with_index do |_z, i|
132
- base1 = 21_203 + i * 9
133
- base2 = 31_007 + i * 3
134
- base3 = 31_200 + i * 3
135
- registers_to_read << (base1..(base1 + 1))
136
- registers_to_read << (base2..(base2 + 2))
137
- registers_to_read << base3
138
- end
139
- else
140
- registers_to_read << 502
141
- registers_to_read << (745..746)
134
+ zones.each do |z|
135
+ registers_to_read.concat(z.registers_to_read)
142
136
  end
137
+ registers_to_read.concat(compressor.registers_to_read)
138
+ registers_to_read.concat(blower.registers_to_read)
139
+ registers_to_read.concat(pump.registers_to_read)
140
+ registers_to_read.concat(dhw.registers_to_read) if dhw
141
+ # need dehumidify mode to calculate final current mode;
142
+ # apparently non-VSD doesn't have this register at all?
143
+ registers_to_read.concat([362]) if compressor.is_a?(Compressor::VSDrive)
143
144
 
144
145
  @faults = @modbus_slave.holding_registers[601..699]
145
146
 
@@ -148,24 +149,11 @@ module Aurora
148
149
 
149
150
  outputs = registers[30]
150
151
 
151
- @dhw_enabled = registers[400]
152
- @dhw_setpoint = registers[401]
153
152
  @entering_air_temperature = registers[740]
154
153
  @relative_humidity = registers[741]
155
154
  @leaving_air_temperature = registers[900]
156
155
  @leaving_water_temperature = registers[1110]
157
156
  @entering_water_temperature = registers[1111]
158
- @dhw_water_temperature = registers[1114]
159
- @waterflow = registers[1117]
160
- @compressor_speed = if vs_drive?
161
- registers[3001]
162
- elsif outputs.include?(:cc2)
163
- 2
164
- elsif outputs.include?(:cc)
165
- 1
166
- else
167
- 0
168
- end
169
157
  @outdoor_temperature = registers[31_003]
170
158
  @fp1 = registers[19]
171
159
  @fp2 = registers[20]
@@ -173,9 +161,7 @@ module Aurora
173
161
  @error = registers[25] & 0x7fff
174
162
  @derated = (41..46).include?(@error)
175
163
  @safe_mode = [47, 48, 49, 72, 74].include?(@error)
176
- @compressor_watts = registers[1147]
177
164
  @aux_heat_watts = registers[1151]
178
- @loop_pump_watts = registers[1165]
179
165
  @total_watts = registers[1153]
180
166
 
181
167
  @current_mode = if outputs.include?(:lockout)
@@ -196,23 +182,13 @@ module Aurora
196
182
  :standby
197
183
  end
198
184
 
199
- blower.refresh(registers)
200
-
201
185
  zones.each do |z|
202
186
  z.refresh(registers)
203
187
  end
204
- end
205
-
206
- def blower_only_ecm_speed=(value)
207
- return unless (1..12).include?(value)
208
-
209
- @modbus_slave.holding_registers[340] = value
210
- end
211
-
212
- def aux_heat_ecm_speed=(value)
213
- return unless (1..12).include?(value)
214
-
215
- @modbus_slave.holding_registers[347] = value
188
+ compressor.refresh(registers)
189
+ blower.refresh(registers)
190
+ pump.refresh(registers)
191
+ dhw&.refresh(registers)
216
192
  end
217
193
 
218
194
  def cooling_airflow_adjustment=(value)
@@ -220,34 +196,10 @@ module Aurora
220
196
  @modbus_slave.holding_registers[346] = value
221
197
  end
222
198
 
223
- def dhw_enabled=(value)
224
- @modbus_slave.holding_registers[400] = value ? 1 : 0
225
- end
226
-
227
- def dhw_setpoint=(value)
228
- raise ArgumentError unless (100..140).include?(value)
229
-
230
- @modbus_slave.holding_registers[401] = (value * 10).to_i
231
- end
232
-
233
199
  def loop_pressure_trip=(value)
234
200
  @modbus_slave.holding_registers[419] = (value * 10).to_i
235
201
  end
236
202
 
237
- def vs_pump_control=(value)
238
- raise ArgumentError unless (value = VS_PUMP_CONTROL.invert[value])
239
-
240
- @modbus_slave.holding_registers[323] = value
241
- end
242
-
243
- def vs_pump_min=(value)
244
- @modbus_slave.holding_registers[321] = value
245
- end
246
-
247
- def vs_pump_max=(value)
248
- @modbus_slave.holding_registers[322] = value
249
- end
250
-
251
203
  def line_voltage=(value)
252
204
  raise ArgumentError unless (90..635).include?(value)
253
205
 
@@ -289,14 +241,6 @@ module Aurora
289
241
  @energy_monitor == 2
290
242
  end
291
243
 
292
- def vs_drive?
293
- @program == "ABCVSP"
294
- end
295
-
296
- def dhw?
297
- (-999..999).include?(dhw_water_temperature)
298
- end
299
-
300
244
  # config aurora system
301
245
  { thermostat: 800, axb: 806, iz2: 812, aoc: 815, moc: 818, eev2: 824 }.each do |(component, register)|
302
246
  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,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
@@ -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
 
@@ -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
- @abc.modbus_slave.holding_registers[21_202 + (zone_number - 1) * 9] = raw_value
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
- @abc.modbus_slave.holding_registers[21_205 + (zone_number - 1) * 9] = raw_value
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
- @abc.modbus_slave.holding_registers[21_206 + (zone_number - 1) * 9] = value
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
- @abc.modbus_slave.holding_registers[21_207 + (zone_number - 1) * 9] = value
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
- @abc.modbus_slave.holding_registers[21_203 + (zone_number - 1) * 9] = raw_value
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
- @abc.modbus_slave.holding_registers[21_204 + (zone_number - 1) * 9] = value
82
- @cooling_target_temperature = raw_value
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
@@ -44,5 +44,6 @@ module Aurora
44
44
  def write_holding_register(addr, value)
45
45
  @registers[addr] = value
46
46
  end
47
+ alias []= write_holding_register
47
48
  end
48
49
  end
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "aurora/component"
4
+
5
+ module Aurora
6
+ module Pump
7
+ class GenericPump < Component
8
+ attr_reader :type, :watts, :waterflow
9
+
10
+ def initialize(abc, type)
11
+ super(abc)
12
+ @type = type
13
+ end
14
+
15
+ def registers_to_read
16
+ result = [1117]
17
+ result.concat([1164..1165]) if abc.energy_monitoring?
18
+ result
19
+ end
20
+
21
+ def refresh(registers)
22
+ @waterflow = registers[1117]
23
+ @watts = registers[1164] if abc.energy_monitoring?
24
+ end
25
+ end
26
+
27
+ class VSPump < GenericPump
28
+ attr_reader :speed, :minimum_speed, :maximum_speed, :manual_control
29
+ alias manual_control? manual_control
30
+
31
+ def registers_to_read
32
+ super + [321..325]
33
+ end
34
+
35
+ def refresh(registers)
36
+ super
37
+ @minimum_speed = registers[321]
38
+ @maximum_speed = registers[322]
39
+ @manual_control = registers[323] != :off
40
+ @speed = registers[325]
41
+ end
42
+
43
+ def manual_control=(value)
44
+ holding_registers[323] = value ? speed : 0x7fff
45
+ end
46
+
47
+ { speed: 323,
48
+ minimum_speed: 321,
49
+ maximum_speed: 322 }.each do |(setting, register)|
50
+ class_eval <<-RUBY, __FILE__, __LINE__ + 1
51
+ def #{setting}=(value)
52
+ raise ArgumentError unless (1..100).include?(value)
53
+
54
+ holding_registers[#{register}] = value
55
+ end
56
+ RUBY
57
+ end
58
+ end
59
+ end
60
+ end
@@ -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?
@@ -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, 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],
@@ -521,12 +530,13 @@ module Aurora
521
530
  ->(v) { TO_TENTHS.call(NEGATABLE.call(v)) } => [1135, 1136],
522
531
  method(:to_int32) => [1146, 1148, 1150, 1152, 1154, 1156, 1164, 3422, 3424],
523
532
  method(:manual_operation) => [3002],
533
+ method(:thermostat_configuration2) => [12_006],
524
534
  ->(v) { HEATING_MODE[v] } => [12_606, 21_202, 21_211, 21_220, 21_229, 21_238, 21_247],
525
535
  ->(v) { FAN_MODE[v] } => [12_621, 21_205, 21_214, 21_223, 21_232, 21_241, 21_250],
526
536
  ->(v) { from_bitmask(v, HUMIDIFIER_SETTINGS) } => [31_109],
527
537
  ->(v) { { humidification_target: v >> 8, dehumidification_target: v & 0xff } } => [31_110],
528
538
  method(:iz2_demand) => [31_005],
529
- method(:zone_configuration1) => [31_008, 31_011, 31_014, 31_017, 31_020, 31_023],
539
+ method(:zone_configuration1) => [12_005, 31_008, 31_011, 31_014, 31_017, 31_020, 31_023],
530
540
  method(:zone_configuration2) => [31_009, 31_012, 31_015, 31_018, 31_021, 31_024],
531
541
  method(:zone_configuration3) => [31_200, 31_203, 31_206, 31_209, 31_212, 31_215],
532
542
  ->(registers, idx) { to_string(registers, idx, 13) } => [31_400],
@@ -540,7 +550,7 @@ module Aurora
540
550
  REGISTER_FORMATS = {
541
551
  "%ds" => [1, 6, 9, 15, 84, 85, 110],
542
552
  "%dV" => [16, 112, 3331, 3424, 3523],
543
- "%0.1fºF" => [19, 20, 401, 501, 502, 567, 740, 745, 746, 747, 900,
553
+ "%0.1fºF" => [19, 20, 401, 501, 502, 567, 740, 745, 746, 747, 900, 903,
544
554
  1109, 1110, 1111, 1112, 1113, 1114, 1124, 1125, 1134, 1135, 1136,
545
555
  3325, 3326, 3327, 3330, 3522, 3903, 3905, 3906,
546
556
  12_619, 12_620,
@@ -553,8 +563,8 @@ module Aurora
553
563
  31_003,
554
564
  31_007, 31_010, 31_013, 31_016, 31_019, 31_022],
555
565
  "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],
566
+ "%d%%" => [282, 321, 322, 325, 346, 565, 741, 908, 1126, 3332, 3524, 3808],
567
+ "%0.1f psi" => [419, 901, 1115, 1116, 1119, 3322, 3323],
558
568
  "%0.1fA" => [1105, 1106, 1107, 1108],
559
569
  "%0.1fgpm" => [1117],
560
570
  "%dW" => [1146, 1148, 1150, 1152, 1164, 3422],
@@ -812,6 +822,10 @@ module Aurora
812
822
  828 => "AWL Version",
813
823
  829 => "AWL Revision",
814
824
  900 => "Leaving Air",
825
+ 901 => "Suction Pressure",
826
+ 903 => "SuperHeat Temperature",
827
+ 908 => "EEV Open %",
828
+ 909 => "SubCooling (Cooling)",
815
829
  1103 => "AXB Inputs",
816
830
  1104 => "AXB Outputs",
817
831
  1105 => "Blower Amps",
@@ -841,10 +855,6 @@ module Aurora
841
855
  1154 => "Heat of Extraction",
842
856
  1156 => "Heat of Rejection",
843
857
  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
858
  3000 => "Compressor Speed Desired",
849
859
  3001 => "Compressor Speed Actual",
850
860
  3002 => "Manual Operation",
@@ -876,6 +886,14 @@ module Aurora
876
886
  3904 => "VS Drive Leaving Air Temperature?",
877
887
  3905 => "VS Drive Saturated Evaporator Discharge Temperature",
878
888
  3906 => "VS Drive SuperHeat Temperature",
889
+ 12_005 => "Fan Configuration",
890
+ 12_006 => "Heating Mode",
891
+ 12_606 => "Heating Mode (write)",
892
+ 12_619 => "Heating Setpoint (write)",
893
+ 12_620 => "Cooling Setpoint (write)",
894
+ 12_621 => "Fan Mode (write)",
895
+ 12_622 => "Intermittent Fan On Time (write)",
896
+ 12_623 => "Intermittent Fan Off Time (write)",
879
897
  31_003 => "Outdoor Temp",
880
898
  31_005 => "IZ2 Demand",
881
899
  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
- @heating_target_temperature = registers[746]
16
- @cooling_target_temperature = registers[745]
23
+ @heating_target_temperature = registers[745]
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.2"
4
+ VERSION = "0.5.0"
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.2
4
+ version: 0.5.0
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-08-31 00:00:00.000000000 Z
11
+ date: 2021-09-01 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/pump.rb
144
147
  - lib/aurora/registers.rb
145
148
  - lib/aurora/thermostat.rb
146
149
  - lib/aurora/version.rb