waterfurnace_aurora 0.7.5 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 037ee3ea9f7274fbc12400840c5f174a70e42361c686753728b31ef8c4c6faaf
4
- data.tar.gz: 4ce5ddc17e4212d8a73f1c2f9255608d9d9a933da8d74e84af3552acd8bf7021
3
+ metadata.gz: 5a92e9aadd14ee0154443cea52907f793880242ce22a44f3df7912065074c3c8
4
+ data.tar.gz: 553a945493652a50f92f49e4e0620db8e03305c3ff0ab65823f20c15db2ac89d
5
5
  SHA512:
6
- metadata.gz: d21f026be0e2d578b0a12064b5f90c71e039636d6fa7c21cb2a30c87fae2f2a0cc9eb9ffc65ca025cf415800abab3c32185a52ecc1e86ed5c15b93fe9516391c
7
- data.tar.gz: 2dc36c52c465c776f95e6581f60c20816b06c76d392d485c5c14b7c85a82c16b141b68da569eda8bfce1ddfed60bf1027166951b9bbf17383fe8f410506c3e39
6
+ metadata.gz: 5fee6734f5e5752aec571c2c93a81e79be950af8c8f4a3ab3259f7d6b1d5e2540d4fd616be06d911a382c710b23ed43c1b1ecf23db23444022c24216faff52cb
7
+ data.tar.gz: c2e5a9f54f7bd0f6b869ffff94b4cd0a0bf9de65ea29d8342fe690f582893a9fe0a7b0f0ce94ba4c0ecf6595156d21a12e7e8203023aaadc923a56c0951f817f
data/exe/aurora_fetch CHANGED
@@ -9,11 +9,19 @@ require "uri"
9
9
  require "yaml"
10
10
 
11
11
  debug_modbus = yaml = false
12
+ try_individual = nil
12
13
 
13
14
  options = OptionParser.new do |opts|
14
15
  opts.banner = "Usage: aurora_fetch /path/to/serial/port REGISTERS [options]"
15
16
 
17
+ opts.separator("")
18
+ opts.separator("Use `known` to fetch all identified registers. Use `valid` to fetch all registers that will respond.")
19
+ opts.separator("")
20
+
16
21
  opts.on("--debug-modbus", "Print actual protocol bytes") { debug_modbus = true }
22
+ opts.on("--[no-]try-individual",
23
+ "Query registers one-by-one if a range has an illegal address. " \
24
+ "Defaults to true for `valid` and `known` special registers, false otherwise.") { |v| try_individual = v }
17
25
  opts.on("-y", "--yaml", "Output raw values as YAML") { yaml = true }
18
26
  opts.on("-v", "--version", "Print version") do
19
27
  puts Aurora::VERSION
@@ -32,11 +40,13 @@ unless ARGV.length == 2
32
40
  exit 1
33
41
  end
34
42
 
35
- abc = Aurora::ABCClient.new(ARGV[0])
36
- abc.modbus_slave.logger = Logger.new($stdout)
37
- abc.modbus_slave.logger.level = debug_modbus ? :debug : :warn
43
+ modbus_slave = Aurora::ABCClient.open_modbus_slave(ARGV[0])
44
+ modbus_slave.read_retry_timeout = 15
45
+ modbus_slave.read_retries = 2
46
+ modbus_slave.logger = Logger.new($stdout)
47
+ modbus_slave.logger.level = debug_modbus ? :debug : :warn
38
48
 
39
- registers = abc.query_registers(ARGV[1])
49
+ registers = Aurora::ABCClient.query_registers(modbus_slave, ARGV[1], try_individual: try_individual)
40
50
 
41
51
  if yaml
42
52
  puts YAML.dump(registers)
@@ -93,6 +93,7 @@ class MQTTBridge
93
93
  @abc.refresh
94
94
 
95
95
  components = { @homie_abc => @abc,
96
+ @aux_heat => @abc.aux_heat,
96
97
  @compressor => @abc.compressor,
97
98
  @blower => @abc.blower,
98
99
  @pump => @abc.pump,
@@ -131,7 +132,7 @@ class MQTTBridge
131
132
  }
132
133
 
133
134
  @homie_abc = @homie.node("abc", "Heat Pump", "ABC") do |node|
134
- allowed_modes = %w[lockout standby blower heating cooling eh1 eh2 emergency waiting]
135
+ allowed_modes = %w[lockout standby blower heating cooling waiting]
135
136
  allowed_modes << "dehumidify" if @abc.compressor.is_a?(Aurora::Compressor::VSDrive)
136
137
  node.property("current-mode",
137
138
  "Current Heating/Cooling Mode",
@@ -155,14 +156,16 @@ class MQTTBridge
155
156
  hass: { sensor: { device_class: :temperature,
156
157
  state_class: :measurement,
157
158
  entity_category: :diagnostic } })
158
- node.property("leaving-air-temperature",
159
- "Leaving Air Temperature",
160
- :float,
161
- @abc.leaving_air_temperature,
162
- unit: "°F",
163
- hass: { sensor: { device_class: :temperature,
164
- state_class: :measurement,
165
- entity_category: :diagnostic } })
159
+ if @abc.awl_communicating?
160
+ node.property("leaving-air-temperature",
161
+ "Leaving Air Temperature",
162
+ :float,
163
+ @abc.leaving_air_temperature,
164
+ unit: "°F",
165
+ hass: { sensor: { device_class: :temperature,
166
+ state_class: :measurement,
167
+ entity_category: :diagnostic } })
168
+ end
166
169
  node.property("leaving-water-temperature",
167
170
  "Leaving Water Temperature",
168
171
  :float,
@@ -171,7 +174,8 @@ class MQTTBridge
171
174
  hass: { sensor: { device_class: :temperature,
172
175
  state_class: :measurement,
173
176
  entity_category: :diagnostic } })
174
- unless @abc.outdoor_temperature.zero? # TODO: figure out the config if this actually exists
177
+ # TODO: figure out the config if this actually exists
178
+ if @abc.awl_communicating? && !@abc.outdoor_temperature.zero?
175
179
  node.property("outdoor-temperature",
176
180
  "Outdoor Temperature",
177
181
  :float,
@@ -181,6 +185,14 @@ class MQTTBridge
181
185
  state_class: :measurement } })
182
186
  end
183
187
 
188
+ node.property("line-voltage",
189
+ "Line Voltage Setting",
190
+ :integer,
191
+ @abc.line_voltage,
192
+ format: 90..635,
193
+ unit: "V") do |value, property|
194
+ @mutex.synchronize { property.value = @abc.line_voltage = value }
195
+ end
184
196
  node.property("fp1",
185
197
  "FP1 Sensor",
186
198
  :float,
@@ -197,12 +209,11 @@ class MQTTBridge
197
209
  hass: { sensor: { device_class: :temperature,
198
210
  state_class: :measurement,
199
211
  entity_category: :diagnostic } })
200
- %i[aux_heat total].each do |component|
201
- component = "#{component}_watts"
202
- node.property(component.tr("_", "-"),
203
- component.sub("_watts", " Power Usage").tr("_", " ").titleize,
212
+ if @abc.energy_monitoring?
213
+ node.property("watts",
214
+ "Total Power Usage",
204
215
  :integer,
205
- @abc.public_send(component),
216
+ @abc.watts,
206
217
  unit: "W",
207
218
  hass: { sensor: { device_class: :power,
208
219
  state_class: :measurement } })
@@ -236,6 +247,30 @@ class MQTTBridge
236
247
  hass: { sensor: { device_class: :temperature,
237
248
  state_class: :measurement,
238
249
  entity_category: :diagnostic } })
250
+ node.property("drive-temperature",
251
+ "Drive Temperature",
252
+ :float,
253
+ @abc.compressor.drive_temperature,
254
+ unit: "°F",
255
+ hass: { sensor: { device_class: :temperature,
256
+ state_class: :measurement,
257
+ entity_category: :diagnostic } })
258
+ node.property("inverter-temperature",
259
+ "Inverter Temperature",
260
+ :float,
261
+ @abc.compressor.inverter_temperature,
262
+ unit: "°F",
263
+ hass: { sensor: { device_class: :temperature,
264
+ state_class: :measurement,
265
+ entity_category: :diagnostic } })
266
+ node.property("fan-speed",
267
+ "Fan Speed",
268
+ :integer,
269
+ @abc.compressor.fan_speed,
270
+ unit: "%",
271
+ format: 0..100,
272
+ hass: { sensor: { state_class: :measurement,
273
+ entity_category: :diagnostic } })
239
274
 
240
275
  next unless @abc.iz2?
241
276
 
@@ -248,6 +283,25 @@ class MQTTBridge
248
283
  entity_category: :diagnostic } })
249
284
  end
250
285
 
286
+ @aux_heat = @homie.node("aux-heat", "Aux Heater", "Aux Heater") do |node|
287
+ node.property("stage",
288
+ "Current Stage",
289
+ :integer,
290
+ @abc.aux_heat.stage,
291
+ format: 0..2,
292
+ hass: { sensor: { state_class: :measurement } })
293
+
294
+ if @abc.energy_monitoring?
295
+ node.property("watts",
296
+ "Power Usage",
297
+ :integer,
298
+ @abc.aux_heat.watts,
299
+ unit: "W",
300
+ hass: { sensor: { device_class: :power,
301
+ state_class: :measurement } })
302
+ end
303
+ end
304
+
251
305
  @blower = @homie.node("blower", "Blower", @abc.blower.type) do |node|
252
306
  node.property("running",
253
307
  "Running",
@@ -325,15 +379,6 @@ class MQTTBridge
325
379
  :boolean,
326
380
  @abc.pump.running,
327
381
  hass: { binary_sensor: { device_class: :running } })
328
- node.property("speed",
329
- "Speed",
330
- :integer,
331
- @abc.pump.speed,
332
- format: 0..100,
333
- unit: "%",
334
- hass: { number: { entity_category: :diagnostic } }) do |value, property|
335
- @mutex.synchronize { property.value = @abc.pump.speed = value }
336
- end
337
382
  node.property("manual-control",
338
383
  "Manual Control",
339
384
  :boolean,
@@ -359,6 +404,17 @@ class MQTTBridge
359
404
  hass: { number: { entity_category: :config } }) do |value, property|
360
405
  @mutex.synchronize { property.value = @abc.pump.maximum_speed = value }
361
406
  end
407
+ next unless @abc.awl_axb?
408
+
409
+ node.property("speed",
410
+ "Speed",
411
+ :integer,
412
+ @abc.pump.speed,
413
+ format: 0..100,
414
+ unit: "%",
415
+ hass: { number: { entity_category: :diagnostic } }) do |value, property|
416
+ @mutex.synchronize { property.value = @abc.pump.speed = value }
417
+ end
362
418
  end
363
419
 
364
420
  if @abc.dhw
@@ -388,47 +444,41 @@ class MQTTBridge
388
444
  @abc.dhw.set_point,
389
445
  format: 100..140,
390
446
  unit: "°F",
391
- hass: :number) do |value, property|
447
+ hass: { number: { step: 5 } }) do |value, property|
392
448
  @mutex.synchronize { property.value = @abc.dhw.set_point = value }
393
449
  end
394
450
  end
395
451
  end
396
452
 
397
453
  @humidistat = @homie.node("humidistat", "Humidistat", "Humidistat") do |node|
398
- node.property("relative-humidity",
399
- "Relative Humidity",
400
- :integer,
401
- @abc.humidistat.relative_humidity,
402
- unit: "%",
403
- format: 0..100,
404
- hass: { sensor: { device_class: :humidity,
405
- state_class: :measurement } })
406
454
  if @abc.humidistat.humidifier?
407
455
  node.property("humidifier-running",
408
456
  "Humidifier Running",
409
457
  :boolean,
410
458
  @abc.humidistat.humidifier_running?)
411
- node.property("humidifier-mode",
412
- "Humidifier Mode",
413
- :enum,
414
- @abc.humidistat.humidifier_mode,
415
- format: %i[auto manual]) do |value, property|
416
- @mutex.synchronize { property.value = @abc.humidistat.humidifier_mode = value.to_sym }
417
- end
418
- node.property("humidification-target",
419
- "Humidification Target Relative Humidity",
420
- :integer,
421
- @abc.humidistat.humidification_target,
422
- unit: "%",
423
- format: 15..50) do |value, property|
424
- @mutex.synchronize { property.value = @abc.humidistat.humidification_target = value }
459
+ if @abc.awl_communicating?
460
+ node.property("humidifier-mode",
461
+ "Humidifier Mode",
462
+ :enum,
463
+ @abc.humidistat.humidifier_mode,
464
+ format: %i[auto manual]) do |value, property|
465
+ @mutex.synchronize { property.value = @abc.humidistat.humidifier_mode = value.to_sym }
466
+ end
467
+ node.property("humidification-target",
468
+ "Humidification Target Relative Humidity",
469
+ :integer,
470
+ @abc.humidistat.humidification_target,
471
+ unit: "%",
472
+ format: 15..50) do |value, property|
473
+ @mutex.synchronize { property.value = @abc.humidistat.humidification_target = value }
474
+ end
475
+ node.hass_humidifier("humidifier-running",
476
+ target_property: "humidification-target",
477
+ mode_property: "humidifier-mode",
478
+ id: "humidifier",
479
+ name: "Humidifier",
480
+ device_class: :humidifier)
425
481
  end
426
- node.hass_humidifier("humidifier-running",
427
- target_property: "humidification-target",
428
- mode_property: "humidifier-mode",
429
- id: "humidifier",
430
- name: "Humidifier",
431
- device_class: :humidifier)
432
482
  end
433
483
 
434
484
  # VSDrive can perform active dehumidification, even without a dedicated dehumidifier
@@ -438,28 +488,40 @@ class MQTTBridge
438
488
  "Dehumidifier Running",
439
489
  :boolean,
440
490
  @abc.humidistat.dehumidifier_running?)
441
- node.property("dehumidifier-mode",
442
- "Dehumidifier Mode",
443
- :enum,
444
- @abc.humidistat.dehumidifier_mode,
445
- format: %i[auto manual]) do |value, property|
446
- @mutex.synchronize { property.value = @abc.humidistat.dehumidifier_mode = value.to_sym }
447
- end
448
- node.property("dehumidification-target",
449
- "Dehumidification Target Relative Humidity",
450
- :integer,
451
- @abc.humidistat.dehumidification_target,
452
- unit: "%",
453
- format: 35..65) do |value, property|
454
- @mutex.synchronize { property.value = @abc.humidistat.dehumidification_target = value }
491
+ if @abc.awl_communicating?
492
+ node.property("dehumidifier-mode",
493
+ "Dehumidifier Mode",
494
+ :enum,
495
+ @abc.humidistat.dehumidifier_mode,
496
+ format: %i[auto manual]) do |value, property|
497
+ @mutex.synchronize { property.value = @abc.humidistat.dehumidifier_mode = value.to_sym }
498
+ end
499
+ node.property("dehumidification-target",
500
+ "Dehumidification Target Relative Humidity",
501
+ :integer,
502
+ @abc.humidistat.dehumidification_target,
503
+ unit: "%",
504
+ format: 35..65) do |value, property|
505
+ @mutex.synchronize { property.value = @abc.humidistat.dehumidification_target = value }
506
+ end
507
+ node.hass_humidifier("dehumidifier-running",
508
+ target_property: "dehumidification-target",
509
+ mode_property: "dehumidifier-mode",
510
+ id: "dehumidifier",
511
+ name: "Dehumidifier",
512
+ device_class: :dehumidifier)
455
513
  end
456
- node.hass_humidifier("dehumidifier-running",
457
- target_property: "dehumidification-target",
458
- mode_property: "dehumidifier-mode",
459
- id: "dehumidifier",
460
- name: "Dehumidifier",
461
- device_class: :dehumidifier)
462
514
  end
515
+ next unless @abc.awl_communicating?
516
+
517
+ node.property("relative-humidity",
518
+ "Relative Humidity",
519
+ :integer,
520
+ @abc.humidistat.relative_humidity,
521
+ unit: "%",
522
+ format: 0..100,
523
+ hass: { sensor: { device_class: :humidity,
524
+ state_class: :measurement } })
463
525
  end
464
526
 
465
527
  @faults = @homie.node("faults", "Fault History", "ABC") do |node|
@@ -487,6 +549,14 @@ class MQTTBridge
487
549
  @abc.zones.each_with_index do |zone, i|
488
550
  type = zone.is_a?(Aurora::IZ2Zone) ? "IntelliZone 2 Zone" : "Thermostat"
489
551
  @homie.node("zone#{i + 1}", "Zone #{i + 1}", type) do |node|
552
+ node.property("current-mode",
553
+ "Current Heating/Cooling Mode Requested",
554
+ :enum,
555
+ zone.current_mode,
556
+ format: %w[standby h1 h2 h3 c1 c2])
557
+
558
+ next unless @abc.awl_communicating?
559
+
490
560
  allowed_modes = %w[off auto cool heat]
491
561
  allowed_modes << "eheat" if i.zero?
492
562
  node.property("target-mode",
@@ -496,11 +566,6 @@ class MQTTBridge
496
566
  format: allowed_modes) do |value, property|
497
567
  @mutex.synchronize { property.value = zone.target_mode = value.to_sym }
498
568
  end
499
- node.property("current-mode",
500
- "Current Heating/Cooling Mode Requested",
501
- :enum,
502
- zone.current_mode,
503
- format: %w[standby h1 h2 h3 c1 c2])
504
569
  node.property("target-fan-mode",
505
570
  "Target Fan Mode",
506
571
  :enum,
@@ -3,6 +3,7 @@
3
3
  require "yaml"
4
4
  require "uri"
5
5
 
6
+ require "aurora/aux_heat"
6
7
  require "aurora/blower"
7
8
  require "aurora/compressor"
8
9
  require "aurora/dhw"
@@ -40,12 +41,57 @@ module Aurora
40
41
  client = ::ModBus::RTUClient.new(io)
41
42
  client.with_slave(1)
42
43
  end
44
+
45
+ def query_registers(modbus_slave, query, try_individual: false)
46
+ implicit = try_individual
47
+ ranges = query.split(",").map do |addr|
48
+ case addr
49
+ when "known"
50
+ implicit = true
51
+ try_individual = true if try_individual.nil?
52
+ Aurora::REGISTER_NAMES.keys
53
+ when "valid"
54
+ implicit = true
55
+ try_individual = true if try_individual.nil?
56
+ break Aurora::REGISTER_RANGES
57
+ when /^(\d+)(?:\.\.|-)(\d+)$/
58
+ $1.to_i..$2.to_i
59
+ else
60
+ addr.to_i
61
+ end
62
+ end
63
+ queries = Aurora.normalize_ranges(ranges)
64
+ registers = {}
65
+ queries.each do |subquery|
66
+ registers.merge!(modbus_slave.read_multiple_holding_registers(*subquery))
67
+ rescue ::ModBus::Errors::IllegalDataAddress, ::ModBus::Errors::IllegalFunction
68
+ # maybe this unit doesn't respond to all the addresses we want?
69
+ raise unless implicit
70
+
71
+ # try each query individually
72
+ subquery.each do |subsubquery|
73
+ registers.merge!(modbus_slave.read_multiple_holding_registers(subsubquery))
74
+ rescue ::ModBus::Errors::IllegalDataAddress, ::ModBus::Errors::IllegalFunction
75
+ next unless try_individual
76
+
77
+ # seriously?? try each register individually
78
+ subsubquery.each do |i|
79
+ registers[i] = modbus_slave.holding_registers[i]
80
+ rescue ::ModBus::Errors::IllegalDataAddress, ::ModBus::Errors::IllegalFunction
81
+ next
82
+ end
83
+ end
84
+ end
85
+ registers
86
+ end
43
87
  end
44
88
 
45
89
  attr_reader :modbus_slave,
90
+ :abc_version,
46
91
  :model,
47
92
  :serial_number,
48
93
  :zones,
94
+ :aux_heat,
49
95
  :compressor,
50
96
  :blower,
51
97
  :pump,
@@ -60,21 +106,22 @@ module Aurora
60
106
  :outdoor_temperature,
61
107
  :fp1,
62
108
  :fp2,
63
- :aux_heat_watts,
64
- :total_watts
109
+ :line_voltage,
110
+ :watts
65
111
 
66
112
  def initialize(uri)
67
113
  @modbus_slave = self.class.open_modbus_slave(uri)
68
114
  @modbus_slave.read_retry_timeout = 15
69
115
  @modbus_slave.read_retries = 2
70
- raw_registers = @modbus_slave.holding_registers[33, 88...110, 404, 412..413, 1103, 1114]
116
+ raw_registers = @modbus_slave.holding_registers[2, 33, 88...110, 404, 412..413, 813, 1103, 1114]
71
117
  registers = Aurora.transform_registers(raw_registers.dup)
118
+ @abc_version = registers[2]
72
119
  @program = registers[88]
73
120
  @model = registers[92]
74
121
  @serial_number = registers[105]
75
122
  @energy_monitor = raw_registers[412]
76
123
 
77
- @zones = if iz2?
124
+ @zones = if iz2? && iz2_version >= 2.0
78
125
  iz2_zone_count = @modbus_slave.holding_registers[483]
79
126
  (0...iz2_zone_count).map { |i| IZ2Zone.new(self, i + 1) }
80
127
  else
@@ -83,6 +130,7 @@ module Aurora
83
130
 
84
131
  @abc_dipswitches = registers[33]
85
132
  @axb_dipswitches = registers[1103]
133
+ @aux_heat = AuxHeat.new(self)
86
134
  @compressor = if @program == "ABCVSP"
87
135
  Compressor::VSDrive.new(self)
88
136
  else
@@ -108,12 +156,13 @@ module Aurora
108
156
 
109
157
  @faults = []
110
158
 
111
- @registers_to_read = [6, 19..20, 25, 30, 344, 740..741, 900, 1104, 1110..1111, 1114, 1150..1153, 1165,
112
- 31_003]
159
+ @registers_to_read = [6, 19..20, 25, 30, 112, 344, 567, 1104, 1110..1111, 1114, 1150..1153, 1165]
160
+ @registers_to_read.concat([741, 31_003]) if awl_communicating?
161
+ @registers_to_read << 900 if awl_axb?
113
162
  zones.each do |z|
114
163
  @registers_to_read.concat(z.registers_to_read)
115
164
  end
116
- @components = [compressor, blower, pump, dhw, humidistat].compact
165
+ @components = [aux_heat, compressor, blower, pump, dhw, humidistat].compact
117
166
  @components.each do |component|
118
167
  @registers_to_read.concat(component.registers_to_read)
119
168
  end
@@ -122,37 +171,7 @@ module Aurora
122
171
  end
123
172
 
124
173
  def query_registers(query)
125
- implicit = false
126
- ranges = query.split(",").map do |addr|
127
- case addr
128
- when "known"
129
- implicit = true
130
- Aurora::REGISTER_NAMES.keys
131
- when "valid"
132
- implicit = true
133
- break Aurora::REGISTER_RANGES
134
- when /^(\d+)(?:\.\.|-)(\d+)$/
135
- $1.to_i..$2.to_i
136
- else
137
- addr.to_i
138
- end
139
- end
140
- queries = Aurora.normalize_ranges(ranges)
141
- registers = {}
142
- queries.each do |subquery|
143
- registers.merge!(@modbus_slave.read_multiple_holding_registers(*subquery))
144
- rescue ::ModBus::Errors::IllegalDataAddress
145
- # maybe this unit doesn't respond to all the addresses we want?
146
- raise unless implicit
147
-
148
- # try each query individually
149
- subquery.each do |subsubquery|
150
- registers.merge!(@modbus_slave.read_multiple_holding_registers(subsubquery))
151
- rescue ::ModBus::Errors::IllegalDataAddress
152
- next
153
- end
154
- end
155
- registers
174
+ self.class.query_registers(@modbus_slave, query)
156
175
  end
157
176
 
158
177
  def refresh
@@ -164,8 +183,8 @@ module Aurora
164
183
 
165
184
  outputs = registers[30]
166
185
 
167
- @entering_air_temperature = registers[740]
168
- @leaving_air_temperature = registers[900]
186
+ @entering_air_temperature = registers[567]
187
+ @leaving_air_temperature = registers[900] if awl_axb?
169
188
  @leaving_water_temperature = registers[1110]
170
189
  @entering_water_temperature = registers[1111]
171
190
  @outdoor_temperature = registers[31_003]
@@ -175,8 +194,8 @@ module Aurora
175
194
  @error = registers[25] & 0x7fff
176
195
  @derated = (41..46).cover?(@error)
177
196
  @safe_mode = [47, 48, 49, 72, 74].include?(@error)
178
- @aux_heat_watts = registers[1151]
179
- @total_watts = registers[1153]
197
+ @line_voltage = registers[112]
198
+ @watts = registers[1153]
180
199
 
181
200
  @current_mode = if outputs.include?(:lockout)
182
201
  :lockout
@@ -184,10 +203,6 @@ module Aurora
184
203
  :dehumidify
185
204
  elsif outputs.include?(:cc2) || outputs.include?(:cc)
186
205
  outputs.include?(:rv) ? :cooling : :heating
187
- elsif outputs.include?(:eh2)
188
- outputs.include?(:rv) ? :eh2 : :emergency
189
- elsif outputs.include?(:eh1)
190
- outputs.include?(:rv) ? :eh1 : :emergency
191
206
  elsif outputs.include?(:blower)
192
207
  :blower
193
208
  elsif registers[6]
@@ -255,13 +270,18 @@ module Aurora
255
270
  end
256
271
 
257
272
  # config aurora system
258
- { thermostat: 800, axb: 806, iz2: 812, aoc: 815, moc: 818, eev2: 824 }.each do |(component, register)|
273
+ { thermostat: 800, axb: 806, iz2: 812, aoc: 815, moc: 818, eev2: 824, awl: 827 }.each do |(component, register)|
259
274
  class_eval <<-RUBY, __FILE__, __LINE__ + 1
260
275
  def #{component}?
261
276
  return @#{component} if instance_variable_defined?(:@#{component})
262
277
  @#{component} = @modbus_slave.holding_registers[#{register}] != 3
263
278
  end
264
279
 
280
+ def #{component}_version
281
+ return @#{component}_version if instance_variable_defined?(:@#{component}_version)
282
+ @#{component}_version = @modbus_slave.holding_registers[#{register + 1}].to_f / 100
283
+ end
284
+
265
285
  def add_#{component}
266
286
  @modbus_slave.holding_registers[#{register}] = 2
267
287
  end
@@ -272,6 +292,27 @@ module Aurora
272
292
  RUBY
273
293
  end
274
294
 
295
+ # see https://www.waterfurnace.com/literature/symphony/ig2001ew.pdf
296
+ # is there a communicating system compliant with AWL?
297
+ def awl_communicating?
298
+ awl_thermostat? || awl_iz2?
299
+ end
300
+
301
+ # is the thermostat AWL compliant?
302
+ def awl_thermostat?
303
+ thermostat? && thermostat_version >= 3.0
304
+ end
305
+
306
+ # is the IZ2 AWL compliant?
307
+ def awl_iz2?
308
+ iz2? && iz2_version >= 2.0
309
+ end
310
+
311
+ # is the AXB AWL compliant?
312
+ def awl_axb?
313
+ axb? && axb_version >= 2.0
314
+ end
315
+
275
316
  def inspect
276
317
  "#<Aurora::ABCClient #{(instance_variables - [:@modbus_slave]).map do |iv|
277
318
  "#{iv}=#{instance_variable_get(iv).inspect}"
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "aurora/component"
4
+
5
+ module Aurora
6
+ class AuxHeat < Component
7
+ attr_reader :stage, :watts
8
+
9
+ def refresh(registers)
10
+ outputs = registers[30]
11
+ @stage = if outputs.include?(:eh2)
12
+ 2
13
+ elsif outputs.include?(:eh1)
14
+ 1
15
+ else
16
+ 0
17
+ end
18
+ @watts = registers[1151] if abc.energy_monitoring?
19
+ end
20
+ end
21
+ end
data/lib/aurora/blower.rb CHANGED
@@ -36,7 +36,7 @@ module Aurora
36
36
 
37
37
  def refresh(registers)
38
38
  outputs = registers[30]
39
- @speed = if outputs.include?(:eh1)
39
+ @speed = if outputs.include?(:eh1) || outputs.include?(:eh2)
40
40
  4
41
41
  elsif outputs.include?(:cc2)
42
42
  3
@@ -6,6 +6,10 @@ module Aurora
6
6
  @abc = abc
7
7
  end
8
8
 
9
+ def registers_to_read
10
+ []
11
+ end
12
+
9
13
  def inspect
10
14
  "#<Aurora::#{self.class.name} #{(instance_variables - [:@abc]).map do |iv|
11
15
  "#{iv}=#{instance_variable_get(iv).inspect}"
@@ -42,7 +42,7 @@ module Aurora
42
42
  end
43
43
 
44
44
  class VSDrive < GenericCompressor
45
- attr_reader :ambient_temperature, :iz2_desired_speed
45
+ attr_reader :drive_temperature, :inverter_temperature, :ambient_temperature, :iz2_desired_speed, :fan_speed
46
46
 
47
47
  def initialize(abc)
48
48
  super(abc, 12)
@@ -53,7 +53,7 @@ module Aurora
53
53
  end
54
54
 
55
55
  def registers_to_read
56
- result = super + [209, 3001, 3326]
56
+ result = super + [209, 3001, 3326..3327, 3522, 3524]
57
57
  result << 564 if abc.iz2?
58
58
  result
59
59
  end
@@ -63,6 +63,10 @@ module Aurora
63
63
 
64
64
  @speed = registers[3001]
65
65
  @ambient_temperature = registers[3326]
66
+ @drive_temperature = registers[3327]
67
+ @inverter_temperature = registers[3522]
68
+ @fan_speed = registers[3524]
69
+
66
70
  @iz2_desired_speed = registers[564] if abc.iz2?
67
71
  end
68
72
  end
@@ -29,6 +29,8 @@ module Aurora
29
29
  alias_method :dehumidifier_running?, :dehumidifier_running
30
30
 
31
31
  def registers_to_read
32
+ return [] unless @abc.awl_communicating?
33
+
32
34
  result = [741]
33
35
  if humidifier? || dehumidifier? || abc.compressor.is_a?(Compressor::VSDrive)
34
36
  result.concat(abc.iz2? ? [21_114, 31_109..31_110] : [12_309..12_310])
@@ -20,9 +20,14 @@ module Aurora
20
20
  if register.length == 1
21
21
  case register.first
22
22
  when Integer
23
+ missing_register(register.first) unless @registers.key?(register.first)
23
24
  @registers[register.first]
24
25
  when Range
25
- @registers.values_at(*register.first.to_a)
26
+ registers = register.first.to_a
27
+ registers.each do |i|
28
+ missing_register(i) unless @registers.key?(i)
29
+ end
30
+ @registers.values_at(*registers)
26
31
  else
27
32
  raise ArgumentError, "Not implemented yet #{register.inspect}"
28
33
  end
@@ -35,6 +40,11 @@ module Aurora
35
40
  result = {}
36
41
  queries.each do |query|
37
42
  Array(query).each do |i|
43
+ unless @registers.key?(i)
44
+ missing_register(i)
45
+ next
46
+ end
47
+
38
48
  result[i] = @registers[i]
39
49
  end
40
50
  end
@@ -45,5 +55,11 @@ module Aurora
45
55
  @registers[addr] = value
46
56
  end
47
57
  alias_method :[]=, :write_holding_register
58
+
59
+ private
60
+
61
+ def missing_register(idx)
62
+ logger.warn("missing register #{idx}")
63
+ end
48
64
  end
49
65
  end
data/lib/aurora/pump.rb CHANGED
@@ -31,7 +31,7 @@ module Aurora
31
31
  alias_method :manual_control?, :manual_control
32
32
 
33
33
  def registers_to_read
34
- super + [321..325]
34
+ super + (@abc.awl_axb? ? [321..325] : [321..324])
35
35
  end
36
36
 
37
37
  def refresh(registers)
@@ -39,7 +39,7 @@ module Aurora
39
39
  @minimum_speed = registers[321]
40
40
  @maximum_speed = registers[322]
41
41
  @manual_control = registers[323] != :off
42
- @speed = registers[325]
42
+ @speed = registers[325] if @abc.awl_axb?
43
43
  end
44
44
 
45
45
  def manual_control=(value)
@@ -65,7 +65,7 @@ module Aurora
65
65
 
66
66
  def to_string(registers, idx, length)
67
67
  (idx...(idx + length)).map do |i|
68
- raise ArgumentError, "Missing register #{i} for string starting at #{idx}" unless registers[i]
68
+ next "\ufffd" unless registers[i] # missing data? add unicode invalid character
69
69
 
70
70
  (registers[i] >> 8).chr + (registers[i] & 0xff).chr
71
71
  end.join.sub(/[ \0]+$/, "")
@@ -112,7 +112,7 @@ module Aurora
112
112
  53 => "Condensing Pressure Sensor", # Low condensing pressure (PD) or invalid (0 to 870 psi) Retry 10x.
113
113
  54 => "Low Supply Voltage", # Supply Voltage is <180 V (190V to reset) or powered off/on too quickly (<30 sec.).
114
114
  55 => "Out of Envelope", # Comp Operating out of envelope (P0) more than 90 sec. Retry 10x.
115
- 56 => "Drive Over Currnet", # Over current tripped by phase loss, earth fault, short circuit, low water flow, low air flow, or major drive fault. # rubocop:disable Layout/LineLength
115
+ 56 => "Drive Over Current", # Over current tripped by phase loss, earth fault, short circuit, low water flow, low air flow, or major drive fault. # rubocop:disable Layout/LineLength
116
116
  57 => "Drive Over/Under Voltage", # DC Link Voltage to compressor is >450vdc or at minimum voltage (<185vdc).
117
117
  58 => "High Drive Temp", # Drive Temp has reached critical High Temp >239 F
118
118
  59 => "Internal Drive Error", # The MOC has encountered an internal fault or an internal error. Probably fatal.
@@ -867,11 +867,11 @@ module Aurora
867
867
  3225 => "VS Drive Details (Safemode 2)",
868
868
  3226 => "VS Drive Details (Alarm 1)",
869
869
  3227 => "VS Drive Details (Alarm 2)",
870
- 3327 => "VS Drive Temperature",
871
870
  3322 => "VS Drive Discharge Pressure",
872
871
  3323 => "VS Drive Suction Pressure",
873
872
  3325 => "VS Drive Discharge Temperature",
874
873
  3326 => "VS Drive Compressor Ambient Temperature",
874
+ 3327 => "VS Drive Temperature",
875
875
  3330 => "VS Drive Entering Water Temperature",
876
876
  3331 => "VS Drive Line Voltage",
877
877
  3332 => "VS Drive Thermo Power",
@@ -15,19 +15,23 @@ module Aurora
15
15
  :fan_intermittent_off
16
16
 
17
17
  def registers_to_read
18
+ return [31] unless @abc.awl_thermostat?
19
+
18
20
  [31, 502, 745..746, 12_005..12_006]
19
21
  end
20
22
 
21
23
  def refresh(registers)
22
- @ambient_temperature = registers[502]
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]
24
+ if @abc.awl_thermostat?
25
+ @ambient_temperature = registers[502]
26
+ @heating_target_temperature = registers[745]
27
+ @cooling_target_temperature = registers[746]
28
+ config1 = registers[12_005]
29
+ config2 = registers[12_006]
30
+ @target_fan_mode = config1[:fan]
31
+ @fan_intermittent_on = config1[:on_time]
32
+ @fan_intermittent_off = config1[:off_time]
33
+ @target_mode = config2[:mode]
34
+ end
31
35
 
32
36
  inputs = registers[31]
33
37
  @current_fan_mode = inputs.include?(:g)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Aurora
4
- VERSION = "0.7.5"
4
+ VERSION = "1.0.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.7.5
4
+ version: 1.0.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-12-11 00:00:00.000000000 Z
11
+ date: 2022-01-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: ccutrer-serialport
@@ -166,6 +166,7 @@ files:
166
166
  - exe/web_aid_tool
167
167
  - lib/aurora.rb
168
168
  - lib/aurora/abc_client.rb
169
+ - lib/aurora/aux_heat.rb
169
170
  - lib/aurora/blower.rb
170
171
  - lib/aurora/component.rb
171
172
  - lib/aurora/compressor.rb