waterfurnace_aurora 1.0.0 → 1.2.1

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: 5a92e9aadd14ee0154443cea52907f793880242ce22a44f3df7912065074c3c8
4
- data.tar.gz: 553a945493652a50f92f49e4e0620db8e03305c3ff0ab65823f20c15db2ac89d
3
+ metadata.gz: b5bf3ed7c4a651515bad13558d23844c6108ce6ac266b17772e786201ead3255
4
+ data.tar.gz: 7aee648a7e8fdee5efee31ead44ad05a18f8c9df4997f485ff10bf4366f8e887
5
5
  SHA512:
6
- metadata.gz: 5fee6734f5e5752aec571c2c93a81e79be950af8c8f4a3ab3259f7d6b1d5e2540d4fd616be06d911a382c710b23ed43c1b1ecf23db23444022c24216faff52cb
7
- data.tar.gz: c2e5a9f54f7bd0f6b869ffff94b4cd0a0bf9de65ea29d8342fe690f582893a9fe0a7b0f0ce94ba4c0ecf6595156d21a12e7e8203023aaadc923a56c0951f817f
6
+ metadata.gz: 448487c7ee50e3d185c9a8e326806052fbd0c85d0e57a546d65c9a9d1710ea752ceee91e4bc685ca0c7ee256bd6da3bd19d58536d7460861fd1c96ee33be0000
7
+ data.tar.gz: 934d63154af6a18b32d8f996db99c46a8201336cd55ae65781da1eb54700d5e65bb40b9d8c3b99edd56801953eaa330b18f6bf71faa1764bf46d2abc7d6ace2b
data/exe/aurora_fetch CHANGED
@@ -8,20 +8,31 @@ require "optparse"
8
8
  require "uri"
9
9
  require "yaml"
10
10
 
11
- debug_modbus = yaml = false
11
+ debug_modbus = yaml = ignore_missing_registers = false
12
12
  try_individual = nil
13
13
 
14
14
  options = OptionParser.new do |opts|
15
15
  opts.banner = "Usage: aurora_fetch /path/to/serial/port REGISTERS [options]"
16
16
 
17
17
  opts.separator("")
18
- opts.separator("Use `known` to fetch all identified registers. Use `valid` to fetch all registers that will respond.")
18
+ opts.separator(<<~TEXT)
19
+ Use `known` to fetch all identified registers. Use `valid` to fetch all registers
20
+ that should respond. Use `all` to search the entire ModBus address space. Note that
21
+ logging of current progress is only periodic, and does not log every register it's
22
+ trying to fetch.
23
+ TEXT
19
24
  opts.separator("")
20
25
 
21
- opts.on("--debug-modbus", "Print actual protocol bytes") { debug_modbus = true }
26
+ opts.on("--debug-modbus", "Print actual protocol bytes") do
27
+ debug_modbus = true
28
+ end
22
29
  opts.on("--[no-]try-individual",
23
30
  "Query registers one-by-one if a range has an illegal address. " \
24
31
  "Defaults to true for `valid` and `known` special registers, false otherwise.") { |v| try_individual = v }
32
+ opts.on("--ignore-missing-registers",
33
+ "For YAML input only, just log a warning when a register doesn't exist, instead of failing") do
34
+ ignore_missing_registers = true
35
+ end
25
36
  opts.on("-y", "--yaml", "Output raw values as YAML") { yaml = true }
26
37
  opts.on("-v", "--version", "Print version") do
27
38
  puts Aurora::VERSION
@@ -40,11 +51,11 @@ unless ARGV.length == 2
40
51
  exit 1
41
52
  end
42
53
 
43
- modbus_slave = Aurora::ABCClient.open_modbus_slave(ARGV[0])
54
+ modbus_slave = Aurora::ABCClient.open_modbus_slave(ARGV[0], ignore_missing_registers: ignore_missing_registers)
44
55
  modbus_slave.read_retry_timeout = 15
45
56
  modbus_slave.read_retries = 2
46
- modbus_slave.logger = Logger.new($stdout)
47
- modbus_slave.logger.level = debug_modbus ? :debug : :warn
57
+ Aurora.logger = modbus_slave.logger = Logger.new($stderr)
58
+ modbus_slave.logger.level = debug_modbus ? :debug : :info
48
59
 
49
60
  registers = Aurora::ABCClient.query_registers(modbus_slave, ARGV[1], try_individual: try_individual)
50
61
 
@@ -90,30 +90,33 @@ class MQTTBridge
90
90
  loop do
91
91
  begin
92
92
  @mutex.synchronize do
93
- @abc.refresh
94
-
95
- components = { @homie_abc => @abc,
96
- @aux_heat => @abc.aux_heat,
97
- @compressor => @abc.compressor,
98
- @blower => @abc.blower,
99
- @pump => @abc.pump,
100
- @dhw => @abc.dhw,
101
- @humidistat => @abc.humidistat }.compact
102
- @abc.zones.each_with_index do |z, idx|
103
- homie_zone = @homie["zone#{idx + 1}"]
104
- components[homie_zone] = z
105
- end
93
+ @homie.mqtt.batch_publish do
94
+ @abc.refresh
95
+
96
+ components = { @homie_abc => @abc,
97
+ @aux_heat => @abc.aux_heat,
98
+ @compressor => @abc.compressor,
99
+ @blower => @abc.blower,
100
+ @pump => @abc.pump,
101
+ @dhw => @abc.dhw,
102
+ @humidistat => @abc.humidistat }.compact
103
+ @abc.zones.each_with_index do |z, idx|
104
+ homie_zone = @homie["zone#{idx + 1}"]
105
+ components[homie_zone] = z
106
+ end
106
107
 
107
- components.each do |(node, object)|
108
- node.each do |property|
109
- property.value = object.public_send(property.id.tr("-", "_"))
108
+ components.each do |(node, object)|
109
+ node.each do |property|
110
+ property.value = object.public_send(property.id.tr("-", "_"))
111
+ end
110
112
  end
111
- end
112
113
 
113
- @abc.faults.each_with_index do |fault_count, i|
114
- next if fault_count == 0xffff
114
+ @faults["current"].value = @abc.current_fault
115
+ @abc.faults.each_with_index do |fault_count, i|
116
+ next if fault_count == 0xffff
115
117
 
116
- @faults["e#{i + 1}"].value = fault_count
118
+ @faults["e#{i + 1}"].value = fault_count
119
+ end
117
120
  end
118
121
  end
119
122
  rescue => e
@@ -132,7 +135,7 @@ class MQTTBridge
132
135
  }
133
136
 
134
137
  @homie_abc = @homie.node("abc", "Heat Pump", "ABC") do |node|
135
- allowed_modes = %w[lockout standby blower heating cooling waiting]
138
+ allowed_modes = %w[lockout standby blower heating heating_with_aux emergency_heat cooling waiting]
136
139
  allowed_modes << "dehumidify" if @abc.compressor.is_a?(Aurora::Compressor::VSDrive)
137
140
  node.property("current-mode",
138
141
  "Current Heating/Cooling Mode",
@@ -140,6 +143,31 @@ class MQTTBridge
140
143
  @abc.current_mode,
141
144
  format: allowed_modes,
142
145
  hass: { sensor: { state_class: :measurement } })
146
+ node.property("emergency-shutdown",
147
+ "Emergency Shutdown Requested",
148
+ :boolean,
149
+ @abc.emergency_shutdown?,
150
+ hass: :binary_sensor)
151
+ node.property("load-shed",
152
+ "Load Shed Requested",
153
+ :boolean,
154
+ @abc.load_shed?,
155
+ hass: :binary_sensor)
156
+ node.property("locked-out",
157
+ "Is the heat pump currently locked out?",
158
+ :boolean,
159
+ @abc.locked_out?,
160
+ hass: :binary_sensor)
161
+ node.property("derated",
162
+ "Is the compressor currently running at a derated level?",
163
+ :boolean,
164
+ @abc.derated?,
165
+ hass: :binary_sensor)
166
+ node.property("safe-mode",
167
+ "Is the heat pump currently in safe mode?",
168
+ :boolean,
169
+ @abc.safe_mode?,
170
+ hass: :binary_sensor)
143
171
  node.property("entering-air-temperature",
144
172
  "Entering Air Temperature",
145
173
  :float,
@@ -156,6 +184,17 @@ class MQTTBridge
156
184
  hass: { sensor: { device_class: :temperature,
157
185
  state_class: :measurement,
158
186
  entity_category: :diagnostic } })
187
+ node.property("low-pressure-switch",
188
+ "Low Pressure Switch Status",
189
+ :enum,
190
+ @abc.low_pressure_switch,
191
+ format: %w[open closed])
192
+ node.property("high-pressure-switch",
193
+ "High Pressure Switch Status",
194
+ :enum,
195
+ @abc.high_pressure_switch,
196
+ format: %w[open closed])
197
+
159
198
  if @abc.awl_communicating?
160
199
  node.property("leaving-air-temperature",
161
200
  "Leaving Air Temperature",
@@ -190,21 +229,13 @@ class MQTTBridge
190
229
  :integer,
191
230
  @abc.line_voltage,
192
231
  format: 90..635,
193
- unit: "V") do |value, property|
194
- @mutex.synchronize { property.value = @abc.line_voltage = value }
232
+ unit: "V") do |value|
233
+ @mutex.synchronize { @abc.line_voltage = value }
195
234
  end
196
- node.property("fp1",
197
- "FP1 Sensor",
235
+ node.property("air-coil-temperature",
236
+ "Air Coil Temperature (FP2)",
198
237
  :float,
199
- @abc.fp1,
200
- unit: "°F",
201
- hass: { sensor: { device_class: :temperature,
202
- state_class: :measurement,
203
- entity_category: :diagnostic } })
204
- node.property("fp2",
205
- "FP2 Sensor",
206
- :float,
207
- @abc.fp2,
238
+ @abc.air_coil_temperature,
208
239
  unit: "°F",
209
240
  hass: { sensor: { device_class: :temperature,
210
241
  state_class: :measurement,
@@ -227,6 +258,22 @@ class MQTTBridge
227
258
  @abc.compressor.speed,
228
259
  format: @abc.compressor.speed_range,
229
260
  hass: { sensor: { state_class: :measurement } })
261
+ node.property("cooling-liquid-line-temperature",
262
+ "Cooling Liquid Line Temperature (FP1)",
263
+ :float,
264
+ @abc.compressor.cooling_liquid_line_temperature,
265
+ unit: "°F",
266
+ hass: { sensor: { device_class: :temperature,
267
+ state_class: :measurement,
268
+ entity_category: :diagnostic } })
269
+ node.property("saturated-condensor-discharge-temperature",
270
+ "Saturated Condensor Discharge Temperature",
271
+ :float,
272
+ @abc.compressor.saturated_condensor_discharge_temperature,
273
+ unit: "°F",
274
+ hass: { sensor: { device_class: :temperature,
275
+ state_class: :measurement,
276
+ entity_category: :diagnostic } })
230
277
  if @abc.energy_monitoring?
231
278
  node.property("watts",
232
279
  "Power Usage",
@@ -239,6 +286,12 @@ class MQTTBridge
239
286
 
240
287
  next unless @abc.compressor.is_a?(Aurora::Compressor::VSDrive)
241
288
 
289
+ node.property("desired-speed",
290
+ "Desired Speed",
291
+ :integer,
292
+ @abc.compressor.desired_speed,
293
+ format: @abc.compressor.speed_range,
294
+ hass: { sensor: { state_class: :measurement } })
242
295
  node.property("ambient-temperature",
243
296
  "Ambient Temperature",
244
297
  :float,
@@ -271,6 +324,62 @@ class MQTTBridge
271
324
  format: 0..100,
272
325
  hass: { sensor: { state_class: :measurement,
273
326
  entity_category: :diagnostic } })
327
+ node.property("discharge-temperature",
328
+ "Discharge Temperature",
329
+ :float,
330
+ @abc.compressor.discharge_temperature,
331
+ unit: "°F",
332
+ hass: { sensor: { device_class: :temperature,
333
+ state_class: :measurement,
334
+ entity_category: :diagnostic } })
335
+ node.property("discharge-pressure",
336
+ "Discharge Pressure",
337
+ :float,
338
+ @abc.compressor.discharge_pressure,
339
+ unit: "psi",
340
+ hass: { sensor: { device_class: :pressure,
341
+ state_class: :measurement,
342
+ entity_category: :diagnostic } })
343
+ node.property("suction-temperature",
344
+ "Suction Temperature",
345
+ :float,
346
+ @abc.compressor.suction_temperature,
347
+ unit: "°F",
348
+ hass: { sensor: { device_class: :temperature,
349
+ state_class: :measurement,
350
+ entity_category: :diagnostic } })
351
+ node.property("suction-pressure",
352
+ "Suction Pressure",
353
+ :float,
354
+ @abc.compressor.suction_pressure,
355
+ unit: "psi",
356
+ hass: { sensor: { device_class: :pressure,
357
+ state_class: :measurement,
358
+ entity_category: :diagnostic } })
359
+ node.property("saturated-evaporator-discharge-temperature",
360
+ "Saturated Evaporator Discharge Temperature",
361
+ :float,
362
+ @abc.compressor.saturated_evaporator_discharge_temperature,
363
+ unit: "°F",
364
+ hass: { sensor: { device_class: :temperature,
365
+ state_class: :measurement,
366
+ entity_category: :diagnostic } })
367
+ node.property("superheat-temperature",
368
+ "SuperHeat Temperature",
369
+ :float,
370
+ @abc.compressor.superheat_temperature,
371
+ unit: "°F",
372
+ hass: { sensor: { device_class: :temperature,
373
+ state_class: :measurement,
374
+ entity_category: :diagnostic } })
375
+ node.property("eev-percentage",
376
+ "EEV Open Percent Open",
377
+ :integer,
378
+ @abc.compressor.eev_percentage,
379
+ unit: "%",
380
+ format: 0..100,
381
+ hass: { sensor: { state_class: :measurement,
382
+ entity_category: :diagnostic } })
274
383
 
275
384
  next unless @abc.iz2?
276
385
 
@@ -337,8 +446,8 @@ class MQTTBridge
337
446
  :integer,
338
447
  @abc.blower.public_send(field),
339
448
  format: 1..12,
340
- hass: { number: { entity_category: :config } }) do |value, property|
341
- @mutex.synchronize { property.value = @abc.blower.public_send("#{field}=", value) }
449
+ hass: { number: { entity_category: :config } }) do |value|
450
+ @mutex.synchronize { @abc.blower.public_send("#{field}=", value) }
342
451
  end
343
452
  end
344
453
 
@@ -383,8 +492,8 @@ class MQTTBridge
383
492
  "Manual Control",
384
493
  :boolean,
385
494
  @abc.pump.manual_control?,
386
- hass: { switch: { entity_category: :diagnostic } }) do |value, property|
387
- @mutex.synchronize { property.value = @abc.pump.manual_control = value }
495
+ hass: { switch: { entity_category: :diagnostic } }) do |value|
496
+ @mutex.synchronize { @abc.pump.manual_control = value }
388
497
  end
389
498
  node.property("minimum-speed",
390
499
  "Actual Minimum Speed",
@@ -392,8 +501,8 @@ class MQTTBridge
392
501
  @abc.pump.minimum_speed,
393
502
  format: 0..100,
394
503
  unit: "%",
395
- hass: { number: { entity_category: :config } }) do |value, property|
396
- @mutex.synchronize { property.value = @abc.pump.minimum_speed = value }
504
+ hass: { number: { entity_category: :config } }) do |value|
505
+ @mutex.synchronize { @abc.pump.minimum_speed = value }
397
506
  end
398
507
  node.property("maximum-speed",
399
508
  "Actual Maximum Speed",
@@ -401,8 +510,8 @@ class MQTTBridge
401
510
  @abc.pump.minimum_speed,
402
511
  format: 0..100,
403
512
  unit: "%",
404
- hass: { number: { entity_category: :config } }) do |value, property|
405
- @mutex.synchronize { property.value = @abc.pump.maximum_speed = value }
513
+ hass: { number: { entity_category: :config } }) do |value|
514
+ @mutex.synchronize { @abc.pump.maximum_speed = value }
406
515
  end
407
516
  next unless @abc.awl_axb?
408
517
 
@@ -412,8 +521,8 @@ class MQTTBridge
412
521
  @abc.pump.speed,
413
522
  format: 0..100,
414
523
  unit: "%",
415
- hass: { number: { entity_category: :diagnostic } }) do |value, property|
416
- @mutex.synchronize { property.value = @abc.pump.speed = value }
524
+ hass: { number: { entity_category: :diagnostic } }) do |value|
525
+ @mutex.synchronize { @abc.pump.speed = value }
417
526
  end
418
527
  end
419
528
 
@@ -423,8 +532,8 @@ class MQTTBridge
423
532
  "Enabled",
424
533
  :boolean,
425
534
  @abc.dhw.enabled,
426
- hass: { switch: { icon: "mdi:water-boiler" } }) do |value, property|
427
- @mutex.synchronize { property.value = @abc.dhw.enabled = value }
535
+ hass: { switch: { icon: "mdi:water-boiler" } }) do |value|
536
+ @mutex.synchronize { @abc.dhw.enabled = value }
428
537
  end
429
538
  node.property("running",
430
539
  "Pump Running",
@@ -444,8 +553,8 @@ class MQTTBridge
444
553
  @abc.dhw.set_point,
445
554
  format: 100..140,
446
555
  unit: "°F",
447
- hass: { number: { step: 5 } }) do |value, property|
448
- @mutex.synchronize { property.value = @abc.dhw.set_point = value }
556
+ hass: { number: { step: 5 } }) do |value|
557
+ @mutex.synchronize { @abc.dhw.set_point = value }
449
558
  end
450
559
  end
451
560
  end
@@ -461,16 +570,16 @@ class MQTTBridge
461
570
  "Humidifier Mode",
462
571
  :enum,
463
572
  @abc.humidistat.humidifier_mode,
464
- format: %i[auto manual]) do |value, property|
465
- @mutex.synchronize { property.value = @abc.humidistat.humidifier_mode = value.to_sym }
573
+ format: %i[auto manual]) do |value|
574
+ @mutex.synchronize { @abc.humidistat.humidifier_mode = value.to_sym }
466
575
  end
467
576
  node.property("humidification-target",
468
577
  "Humidification Target Relative Humidity",
469
578
  :integer,
470
579
  @abc.humidistat.humidification_target,
471
580
  unit: "%",
472
- format: 15..50) do |value, property|
473
- @mutex.synchronize { property.value = @abc.humidistat.humidification_target = value }
581
+ format: 15..50) do |value|
582
+ @mutex.synchronize { @abc.humidistat.humidification_target = value }
474
583
  end
475
584
  node.hass_humidifier("humidifier-running",
476
585
  target_property: "humidification-target",
@@ -493,16 +602,16 @@ class MQTTBridge
493
602
  "Dehumidifier Mode",
494
603
  :enum,
495
604
  @abc.humidistat.dehumidifier_mode,
496
- format: %i[auto manual]) do |value, property|
497
- @mutex.synchronize { property.value = @abc.humidistat.dehumidifier_mode = value.to_sym }
605
+ format: %i[auto manual]) do |value|
606
+ @mutex.synchronize { @abc.humidistat.dehumidifier_mode = value.to_sym }
498
607
  end
499
608
  node.property("dehumidification-target",
500
609
  "Dehumidification Target Relative Humidity",
501
610
  :integer,
502
611
  @abc.humidistat.dehumidification_target,
503
612
  unit: "%",
504
- format: 35..65) do |value, property|
505
- @mutex.synchronize { property.value = @abc.humidistat.dehumidification_target = value }
613
+ format: 35..65) do |value|
614
+ @mutex.synchronize { @abc.humidistat.dehumidification_target = value }
506
615
  end
507
616
  node.hass_humidifier("dehumidifier-running",
508
617
  target_property: "dehumidification-target",
@@ -525,6 +634,12 @@ class MQTTBridge
525
634
  end
526
635
 
527
636
  @faults = @homie.node("faults", "Fault History", "ABC") do |node|
637
+ node.property("current",
638
+ "Current fault",
639
+ :integer,
640
+ @abc.current_fault,
641
+ format: 0..99,
642
+ hass: :sensor)
528
643
  node.property("clear-history",
529
644
  "Reset Fault Counts",
530
645
  :enum,
@@ -563,15 +678,15 @@ class MQTTBridge
563
678
  "Target Heating/Cooling Mode",
564
679
  :enum,
565
680
  zone.target_mode,
566
- format: allowed_modes) do |value, property|
567
- @mutex.synchronize { property.value = zone.target_mode = value.to_sym }
681
+ format: allowed_modes) do |value|
682
+ @mutex.synchronize { zone.target_mode = value.to_sym }
568
683
  end
569
684
  node.property("target-fan-mode",
570
685
  "Target Fan Mode",
571
686
  :enum,
572
687
  zone.target_fan_mode,
573
- format: %w[auto continuous intermittent]) do |value, property|
574
- @mutex.synchronize { property.value = zone.target_fan_mode = value.to_sym }
688
+ format: %w[auto continuous intermittent]) do |value|
689
+ @mutex.synchronize { zone.target_fan_mode = value.to_sym }
575
690
  end
576
691
  node.property("fan-intermittent-on",
577
692
  "Fan Intermittent Mode On Duration",
@@ -579,8 +694,8 @@ class MQTTBridge
579
694
  zone.fan_intermittent_on,
580
695
  unit: "M",
581
696
  format: %w[0 5 10 15 20],
582
- hass: { select: { entity_category: :config } }) do |value, property|
583
- @mutex.synchronize { property.value = zone.fan_intermittent_on = value.to_i }
697
+ hass: { select: { entity_category: :config } }) do |value|
698
+ @mutex.synchronize { zone.fan_intermittent_on = value.to_i }
584
699
  end
585
700
  node.property("fan-intermittent-off",
586
701
  "Fan Intermittent Mode Off Duration",
@@ -588,8 +703,8 @@ class MQTTBridge
588
703
  zone.fan_intermittent_on,
589
704
  unit: "M",
590
705
  format: %w[0 5 10 15 20 25 30 35 40],
591
- hass: { select: { entity_category: :config } }) do |value, property|
592
- @mutex.synchronize { property.value = zone.fan_intermittent_on = value.to_i }
706
+ hass: { select: { entity_category: :config } }) do |value|
707
+ @mutex.synchronize { zone.fan_intermittent_on = value.to_i }
593
708
  end
594
709
  node.property("current-fan-mode",
595
710
  "Current Fan Status",
@@ -630,15 +745,15 @@ class MQTTBridge
630
745
  :integer,
631
746
  zone.heating_target_temperature,
632
747
  unit: "°F",
633
- format: 40..90) do |value, property|
634
- @mutex.synchronize { property.value = zone.heating_target_temperature = value }
748
+ format: 40..90) do |value|
749
+ @mutex.synchronize { zone.heating_target_temperature = value }
635
750
  end
636
751
  node.property("cooling-target-temperature",
637
752
  "Cooling Target Temperature", :integer,
638
753
  zone.cooling_target_temperature,
639
754
  unit: "°F",
640
- format: 54..99) do |value, property|
641
- @mutex.synchronize { property.value = zone.cooling_target_temperature = value }
755
+ format: 54..99) do |value|
756
+ @mutex.synchronize { zone.cooling_target_temperature = value }
642
757
  end
643
758
  node.hass_climate(action_property: "current-mode",
644
759
  current_temperature_property: "ambient-temperature",
@@ -669,12 +784,12 @@ class MQTTBridge
669
784
  end
670
785
 
671
786
  log_level = ARGV.include?("--debug") ? :debug : :warn
672
- logger = Logger.new($stdout)
673
- logger.level = log_level
674
- abc.modbus_slave.logger = logger
787
+ Aurora.logger = Logger.new($stdout)
788
+ Aurora.logger.level = log_level
789
+ abc.modbus_slave.logger = Aurora.logger
675
790
 
676
791
  device = "aurora-#{abc.serial_number}"
677
792
  homie = MQTT::Homie::Device.new(device, "WaterFurnace", mqtt: mqtt_uri)
678
- homie.logger = logger
793
+ homie.logger = Aurora.logger
679
794
 
680
795
  MQTTBridge.new(abc, homie)
data/exe/web_aid_tool CHANGED
@@ -7,13 +7,17 @@ require "logger"
7
7
  require "optparse"
8
8
  require "yaml"
9
9
 
10
- debug_modbus = monitor = false
10
+ debug_modbus = monitor = ignore_missing_registers = false
11
11
 
12
12
  options = OptionParser.new do |opts|
13
13
  opts.banner = "Usage: web_aid_tool /path/to/serial/port [options]"
14
14
 
15
15
  opts.on("--debug-modbus", "Print actual protocol bytes") { debug_modbus = true }
16
16
  opts.on("--monitor", "Print interpreted registers as they are requested, like aurora_monitor") { monitor = true }
17
+ opts.on("--ignore-missing-registers",
18
+ "For YAML input only, just log a warning when a register doesn't exist, instead of failing") do
19
+ ignore_missing_registers = true
20
+ end
17
21
  opts.on("-v", "--version", "Print version") do
18
22
  puts Aurora::VERSION
19
23
  exit
@@ -31,7 +35,7 @@ unless ARGV.length == 1
31
35
  exit 1
32
36
  end
33
37
 
34
- slave = Aurora::ABCClient.open_modbus_slave(ARGV[0])
38
+ slave = Aurora::ABCClient.open_modbus_slave(ARGV[0], ignore_missing_registers: ignore_missing_registers)
35
39
  slave.logger = Logger.new($stdout)
36
40
  slave.logger.level = debug_modbus ? :debug : :warn
37
41
 
@@ -15,7 +15,7 @@ require "aurora/thermostat"
15
15
  module Aurora
16
16
  class ABCClient
17
17
  class << self
18
- def open_modbus_slave(uri)
18
+ def open_modbus_slave(uri, ignore_missing_registers: false)
19
19
  uri = URI.parse(uri)
20
20
 
21
21
  io = case uri.scheme
@@ -32,7 +32,10 @@ module Aurora
32
32
  require "aurora/mqtt_modbus"
33
33
  return Aurora::MQTTModBus.new(uri)
34
34
  else
35
- return Aurora::MockABC.new(YAML.load_file(uri.path)) if File.file?(uri.path)
35
+ if File.file?(uri.path)
36
+ return Aurora::MockABC.new(YAML.load_file(uri.path),
37
+ ignore_missing_registers: ignore_missing_registers)
38
+ end
36
39
 
37
40
  require "ccutrer-serialport"
38
41
  CCutrer::SerialPort.new(uri.path, baud: 19_200, parity: :even)
@@ -54,6 +57,10 @@ module Aurora
54
57
  implicit = true
55
58
  try_individual = true if try_individual.nil?
56
59
  break Aurora::REGISTER_RANGES
60
+ when "all"
61
+ implicit = true
62
+ try_individual = true if try_individual.nil?
63
+ break 0..65_535
57
64
  when /^(\d+)(?:\.\.|-)(\d+)$/
58
65
  $1.to_i..$2.to_i
59
66
  else
@@ -62,28 +69,48 @@ module Aurora
62
69
  end
63
70
  queries = Aurora.normalize_ranges(ranges)
64
71
  registers = {}
72
+ last_log_time = nil
65
73
  queries.each do |subquery|
74
+ last_log_time = log_query(last_log_time, subquery.inspect)
66
75
  registers.merge!(modbus_slave.read_multiple_holding_registers(*subquery))
67
- rescue ::ModBus::Errors::IllegalDataAddress, ::ModBus::Errors::IllegalFunction
76
+ rescue ::ModBus::Errors::IllegalDataAddress, ::ModBus::Errors::IllegalFunction, ::ModBus::Errors::ModBusTimeout
68
77
  # maybe this unit doesn't respond to all the addresses we want?
69
78
  raise unless implicit
70
79
 
71
80
  # try each query individually
72
81
  subquery.each do |subsubquery|
82
+ last_log_time = log_query(last_log_time, subsubquery.inspect)
73
83
  registers.merge!(modbus_slave.read_multiple_holding_registers(subsubquery))
74
- rescue ::ModBus::Errors::IllegalDataAddress, ::ModBus::Errors::IllegalFunction
84
+ rescue ::ModBus::Errors::IllegalDataAddress,
85
+ ::ModBus::Errors::IllegalFunction,
86
+ ::ModBus::Errors::ModBusTimeout => e
87
+ raise if e.is_a?(::ModBus::Errors::ModBusTimeout) && !try_individual
75
88
  next unless try_individual
76
89
 
77
90
  # seriously?? try each register individually
78
- subsubquery.each do |i|
91
+ Array(subsubquery).each do |i|
92
+ last_log_time = log_query(last_log_time, i.to_s)
79
93
  registers[i] = modbus_slave.holding_registers[i]
80
94
  rescue ::ModBus::Errors::IllegalDataAddress, ::ModBus::Errors::IllegalFunction
95
+ # don't catch ModBusTimeout here... it should have no problem responding to a single register request
81
96
  next
82
97
  end
83
98
  end
84
99
  end
85
100
  registers
86
101
  end
102
+
103
+ private
104
+
105
+ def log_query(last_log_time, query)
106
+ last_log_time ||= Process.clock_gettime(Process::CLOCK_MONOTONIC)
107
+ now = Process.clock_gettime(Process::CLOCK_MONOTONIC)
108
+ if now - last_log_time > 5
109
+ Aurora&.logger&.info("Fetching register(s) #{query}...")
110
+ last_log_time = now
111
+ end
112
+ last_log_time
113
+ end
87
114
  end
88
115
 
89
116
  attr_reader :modbus_slave,
@@ -97,18 +124,31 @@ module Aurora
97
124
  :pump,
98
125
  :dhw,
99
126
  :humidistat,
127
+ :current_fault,
100
128
  :faults,
129
+ :locked_out,
130
+ :derated,
131
+ :safe_mode,
101
132
  :current_mode,
133
+ :low_pressure_switch,
134
+ :high_pressure_switch,
135
+ :emergency_shutdown,
136
+ :load_shed,
102
137
  :entering_air_temperature,
103
138
  :leaving_air_temperature,
104
139
  :leaving_water_temperature,
105
140
  :entering_water_temperature,
106
141
  :outdoor_temperature,
107
- :fp1,
108
- :fp2,
142
+ :air_coil_temperature,
109
143
  :line_voltage,
110
144
  :watts
111
145
 
146
+ alias_method :emergency_shutdown?, :emergency_shutdown
147
+ alias_method :load_shed?, :load_shed
148
+ alias_method :locked_out?, :locked_out
149
+ alias_method :derated?, :derated
150
+ alias_method :safe_mode?, :safe_mode
151
+
112
152
  def initialize(uri)
113
153
  @modbus_slave = self.class.open_modbus_slave(uri)
114
154
  @modbus_slave.read_retry_timeout = 15
@@ -156,7 +196,9 @@ module Aurora
156
196
 
157
197
  @faults = []
158
198
 
159
- @registers_to_read = [6, 19..20, 25, 30, 112, 344, 567, 1104, 1110..1111, 1114, 1150..1153, 1165]
199
+ @entering_air_register = awl_axb? ? 740 : 567
200
+ @registers_to_read = [6, 19..20, 25, 30..31, 112, 344, @entering_air_register, 1104, 1110..1111, 1114, 1150..1153,
201
+ 1165]
160
202
  @registers_to_read.concat([741, 31_003]) if awl_communicating?
161
203
  @registers_to_read << 900 if awl_axb?
162
204
  zones.each do |z|
@@ -183,17 +225,20 @@ module Aurora
183
225
 
184
226
  outputs = registers[30]
185
227
 
186
- @entering_air_temperature = registers[567]
228
+ @entering_air_temperature = registers[@entering_air_register]
187
229
  @leaving_air_temperature = registers[900] if awl_axb?
188
230
  @leaving_water_temperature = registers[1110]
189
231
  @entering_water_temperature = registers[1111]
190
232
  @outdoor_temperature = registers[31_003]
191
- @fp1 = registers[19]
192
- @fp2 = registers[20]
193
- @locked_out = registers[25] & 0x8000
194
- @error = registers[25] & 0x7fff
195
- @derated = (41..46).cover?(@error)
196
- @safe_mode = [47, 48, 49, 72, 74].include?(@error)
233
+ @air_coil_temperature = registers[20]
234
+ @locked_out = !(registers[25] & 0x8000).zero?
235
+ @current_fault = registers[25] & 0x7fff
236
+ @derated = (41..46).cover?(@current_fault)
237
+ @safe_mode = [47, 48, 49, 72, 74].include?(@current_fault)
238
+ @low_pressure_switch = registers[31][:lps]
239
+ @high_pressure_switch = registers[31][:hps]
240
+ @emergency_shutdown = !!registers[31][:emergency_shutdown]
241
+ @load_shed = !!registers[31][:load_shed]
197
242
  @line_voltage = registers[112]
198
243
  @watts = registers[1153]
199
244
 
@@ -202,10 +247,18 @@ module Aurora
202
247
  elsif registers[362]
203
248
  :dehumidify
204
249
  elsif outputs.include?(:cc2) || outputs.include?(:cc)
205
- outputs.include?(:rv) ? :cooling : :heating
250
+ if outputs.include?(:rv)
251
+ :cooling
252
+ elsif outputs.include?(:eh2) || outputs.include?(:eh1)
253
+ :heating_with_aux
254
+ else
255
+ :heating
256
+ end
257
+ elsif outputs.include?(:eh2) || outputs.include?(:eh1)
258
+ :emergency_heat
206
259
  elsif outputs.include?(:blower)
207
260
  :blower
208
- elsif registers[6]
261
+ elsif !registers[6].zero?
209
262
  :waiting
210
263
  else
211
264
  :standby
@@ -3,19 +3,19 @@
3
3
  require "aurora/component"
4
4
 
5
5
  module Aurora
6
- class AuxHeat < Component
6
+ class AuxHeat < Component
7
7
  attr_reader :stage, :watts
8
8
 
9
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?
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
19
  end
20
20
  end
21
21
  end
@@ -5,7 +5,7 @@ require "aurora/component"
5
5
  module Aurora
6
6
  module Compressor
7
7
  class GenericCompressor < Component
8
- attr_reader :speed, :watts
8
+ attr_reader :speed, :watts, :cooling_liquid_line_temperature, :saturated_condensor_discharge_temperature
9
9
 
10
10
  def initialize(abc, stages)
11
11
  super(abc)
@@ -21,11 +21,9 @@ module Aurora
21
21
  end
22
22
 
23
23
  def registers_to_read
24
- if abc.energy_monitoring?
25
- [1146..1147]
26
- else
27
- []
28
- end
24
+ result = [19, 1134]
25
+ result << (1146..1147) if abc.energy_monitoring?
26
+ result
29
27
  end
30
28
 
31
29
  def refresh(registers)
@@ -37,12 +35,26 @@ module Aurora
37
35
  else
38
36
  0
39
37
  end
38
+ @cooling_liquid_line_temperature = registers[19]
39
+ @saturated_condensor_discharge_temperature = registers[1134]
40
40
  @watts = registers[1146] if abc.energy_monitoring?
41
41
  end
42
42
  end
43
43
 
44
44
  class VSDrive < GenericCompressor
45
- attr_reader :drive_temperature, :inverter_temperature, :ambient_temperature, :iz2_desired_speed, :fan_speed
45
+ attr_reader :drive_temperature,
46
+ :inverter_temperature,
47
+ :ambient_temperature,
48
+ :desired_speed,
49
+ :iz2_desired_speed,
50
+ :fan_speed,
51
+ :discharge_pressure,
52
+ :discharge_temperature,
53
+ :suction_pressure,
54
+ :suction_temperature,
55
+ :saturated_evaporator_discharge_temperature,
56
+ :superheat_temperature,
57
+ :eev_percentage
46
58
 
47
59
  def initialize(abc)
48
60
  super(abc, 12)
@@ -53,7 +65,7 @@ module Aurora
53
65
  end
54
66
 
55
67
  def registers_to_read
56
- result = super + [209, 3001, 3326..3327, 3522, 3524]
68
+ result = super + [209, 3000..3001, 3322..3327, 3522, 3524, 3808, 3903..3906]
57
69
  result << 564 if abc.iz2?
58
70
  result
59
71
  end
@@ -61,11 +73,19 @@ module Aurora
61
73
  def refresh(registers)
62
74
  super
63
75
 
76
+ @desired_speed = registers[3000]
64
77
  @speed = registers[3001]
78
+ @discharge_pressure = registers[3322]
79
+ @suction_pressure = registers[3323]
80
+ @discharge_temperature = registers[3325]
65
81
  @ambient_temperature = registers[3326]
66
82
  @drive_temperature = registers[3327]
67
83
  @inverter_temperature = registers[3522]
68
84
  @fan_speed = registers[3524]
85
+ @eev_percentage = registers[3808]
86
+ @suction_temperature = registers[3903]
87
+ @saturated_evaporator_discharge_temperature = registers[3905]
88
+ @superheat_temperature = registers[3906]
69
89
 
70
90
  @iz2_desired_speed = registers[564] if abc.iz2?
71
91
  end
data/lib/aurora/dhw.rb CHANGED
@@ -27,7 +27,6 @@ module Aurora
27
27
 
28
28
  raw_value = (value * 10).to_i
29
29
  holding_registers[401] = raw_value
30
- @set_point = value
31
30
  end
32
31
  end
33
32
  end
@@ -75,8 +75,6 @@ module Aurora
75
75
  raw_value |= 0x4000 if humidifier_mode == :auto
76
76
  raw_value |= 0x8000 if dehumidifier_mode == :auto
77
77
  holding_registers[abc.iz2? ? 21_114 : 12_309] = raw_value
78
- @humidifier_mode = humidifier_mode
79
- @dehumidifier_mode = dehumidifier_mode
80
78
  end
81
79
 
82
80
  def humidification_target=(value)
@@ -92,8 +90,6 @@ module Aurora
92
90
  raise ArgumentError unless (35..65).cover?(dehumidification_target)
93
91
 
94
92
  holding_registers[abc.iz2? ? 21_115 : 12_310] = (humidification_target << 8) + dehumidification_target
95
- @humidification_target = humidification_target
96
- @dehumidification_target = dehumidification_target
97
93
  end
98
94
  end
99
95
  end
@@ -46,28 +46,24 @@ module Aurora
46
46
  return unless (raw_value = Aurora::HEATING_MODE.invert[value])
47
47
 
48
48
  holding_registers[21_202 + ((zone_number - 1) * 9)] = raw_value
49
- @target_mode = value
50
49
  end
51
50
 
52
51
  def target_fan_mode=(value)
53
52
  return unless (raw_value = Aurora::FAN_MODE.invert[value])
54
53
 
55
54
  holding_registers[21_205 + ((zone_number - 1) * 9)] = raw_value
56
- @target_fan_mode = value
57
55
  end
58
56
 
59
57
  def fan_intermittent_on=(value)
60
58
  return unless value >= 0 && value <= 25 && (value % 5).zero?
61
59
 
62
60
  holding_registers[21_206 + ((zone_number - 1) * 9)] = value
63
- @fan_intermittent_on = value
64
61
  end
65
62
 
66
63
  def fan_intermittent_off=(value)
67
64
  return unless value >= 0 && value <= 40 && (value % 5).zero?
68
65
 
69
66
  holding_registers[21_207 + ((zone_number - 1) * 9)] = value
70
- @fan_intermittent_off = value
71
67
  end
72
68
 
73
69
  def heating_target_temperature=(value)
@@ -75,7 +71,6 @@ module Aurora
75
71
 
76
72
  raw_value = (value * 10).to_i
77
73
  holding_registers[21_203 + ((zone_number - 1) * 9)] = raw_value
78
- @heating_target_temperature = value
79
74
  end
80
75
 
81
76
  def cooling_target_temperature=(value)
@@ -83,7 +78,6 @@ module Aurora
83
78
 
84
79
  raw_value = (value * 10).to_i
85
80
  holding_registers[21_204 + ((zone_number - 1) * 9)] = raw_value
86
- @cooling_target_temperature = value
87
81
  end
88
82
  end
89
83
  end
@@ -4,7 +4,8 @@ module Aurora
4
4
  class MockABC
5
5
  attr_accessor :logger
6
6
 
7
- def initialize(registers)
7
+ def initialize(registers, ignore_missing_registers: false)
8
+ @ignore_missing_registers = ignore_missing_registers
8
9
  @registers = registers
9
10
  end
10
11
 
@@ -59,6 +60,8 @@ module Aurora
59
60
  private
60
61
 
61
62
  def missing_register(idx)
63
+ raise ::ModBus::Errors::IllegalDataAddress unless @ignore_missing_registers
64
+
62
65
  logger.warn("missing register #{idx}")
63
66
  end
64
67
  end
@@ -60,6 +60,7 @@ module Aurora
60
60
  end
61
61
 
62
62
  def to_int32(registers, idx)
63
+ Aurora&.logger&.warn("Missing register #{idx + 1}") unless registers[idx + 1]
63
64
  (registers[idx] << 16) + registers[idx + 1]
64
65
  end
65
66
 
@@ -229,6 +230,8 @@ module Aurora
229
230
  0x08 => :blower,
230
231
  0x10 => :eh1,
231
232
  0x20 => :eh2,
233
+ # 0x40 => ??, # this turns on and off quite a bit during normal operation
234
+ # 0x80 => ??, # this turns on occasionally during normal operation; I've only seen it when aux heat is on
232
235
  0x200 => :accessory,
233
236
  0x400 => :lockout,
234
237
  0x800 => :alarm
@@ -703,10 +706,11 @@ module Aurora
703
706
  9 => "Compressor Minimum Run Time",
704
707
  15 => "Blower Off Delay",
705
708
  16 => "Line Voltage",
706
- 17 => "Aux/E Heat Stage", # this has some complicated condition based on
707
- # current inputs and outputs on if it should have a value (310 - v) or (130 - v), or be 0
708
- 19 => "FP1 (Cooling Liquid Line) Temperature",
709
- 20 => "FP2",
709
+ 17 => "Aux/E Heat Stage", # this is how long aux/eheat have been requested in seconds
710
+ # when in eheat mode (explicit on the thermostat), it will stage up to eh2 after 130s
711
+ # when in aux mode (thermostat set to heat; compressor at full capacity), it will stage up to eh2 after 310s
712
+ 19 => "Cooling Liquid Line Temperature (FP1)",
713
+ 20 => "Air Coil Temperature (FP2)",
710
714
  21 => "Condensate", # >= 270 normal, otherwise fault
711
715
  25 => "Last Fault Number", # high bit set if locked out
712
716
  26 => "Last Lockout",
@@ -855,7 +859,11 @@ module Aurora
855
859
  1154 => "Heat of Extraction",
856
860
  1156 => "Heat of Rejection",
857
861
  1164 => "Pump Watts",
862
+ # this combines thermostat/iz2 desired speed with manual operation override
858
863
  3000 => "Compressor Speed Desired",
864
+ # this shows the actual speed
865
+ # it can differ from desired during a ramp to the desired speed, or
866
+ # the periodic ramp up to speed 6 that's not visible in the desired speed
859
867
  3001 => "Compressor Speed Actual",
860
868
  3002 => "Manual Operation",
861
869
  3027 => "Compressor Speed",
@@ -881,7 +889,7 @@ module Aurora
881
889
  3523 => "VS Drive UDC Voltage",
882
890
  3524 => "VS Drive Fan Speed",
883
891
  3804 => "VS Drive Details (EEV2 Ctl)",
884
- 3808 => "VS Drive SuperHeat Percent",
892
+ 3808 => "VS Drive EEV2 % Open",
885
893
  3903 => "VS Drive Suction Temperature",
886
894
  3904 => "VS Drive Leaving Air Temperature?",
887
895
  3905 => "VS Drive Saturated Evaporator Discharge Temperature",
@@ -15,9 +15,9 @@ module Aurora
15
15
  :fan_intermittent_off
16
16
 
17
17
  def registers_to_read
18
- return [31] unless @abc.awl_thermostat?
18
+ return [] unless @abc.awl_thermostat?
19
19
 
20
- [31, 502, 745..746, 12_005..12_006]
20
+ [502, 745..746, 12_005..12_006]
21
21
  end
22
22
 
23
23
  def refresh(registers)
@@ -48,14 +48,12 @@ module Aurora
48
48
  return unless (raw_value = HEATING_MODE.invert[value])
49
49
 
50
50
  @abc.modbus_slave.holding_registers[12_606] = raw_value
51
- @target_mode = value
52
51
  end
53
52
 
54
53
  def target_fan_mode=(value)
55
54
  return unless (raw_value = FAN_MODE.invert[value])
56
55
 
57
56
  @abc.modbus_slave.holding_registers[12_621] = raw_value
58
- @target_fan_mode = value
59
57
  end
60
58
 
61
59
  def heating_target_temperature=(value)
@@ -63,7 +61,6 @@ module Aurora
63
61
 
64
62
  raw_value = (value * 10).to_i
65
63
  @abc.modbus_slave.holding_registers[12_619] = raw_value
66
- @heating_target_temperature = value
67
64
  end
68
65
 
69
66
  def cooling_target_temperature=(value)
@@ -71,21 +68,18 @@ module Aurora
71
68
 
72
69
  raw_value = (value * 10).to_i
73
70
  @abc.modbus_slave.holding_registers[12_620] = raw_value
74
- @cooling_target_temperature = value
75
71
  end
76
72
 
77
73
  def fan_intermittent_on=(value)
78
74
  return unless value >= 0 && value <= 25 && (value % 5).zero?
79
75
 
80
76
  holding_registers[12_622] = value
81
- @fan_intermittent_on = value
82
77
  end
83
78
 
84
79
  def fan_intermittent_off=(value)
85
80
  return unless value >= 0 && value <= 40 && (value % 5).zero?
86
81
 
87
82
  holding_registers[12_623] = value
88
- @fan_intermittent_off = value
89
83
  end
90
84
  end
91
85
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Aurora
4
- VERSION = "1.0.0"
4
+ VERSION = "1.2.1"
5
5
  end
data/lib/aurora.rb CHANGED
@@ -15,4 +15,7 @@ ModBus::Client::Slave.prepend(Aurora::ModBus::Slave)
15
15
  ModBus::RTUSlave.prepend(Aurora::ModBus::RTU)
16
16
 
17
17
  module Aurora
18
+ class << self
19
+ attr_accessor :logger
20
+ end
18
21
  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: 1.0.0
4
+ version: 1.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Cody Cutrer
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2022-01-28 00:00:00.000000000 Z
11
+ date: 2022-02-03 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: ccutrer-serialport