waterfurnace_aurora 0.3.0 → 0.3.4

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: 353f714b82fb803a5a816781e39f1e952fea04485fa09d32f36f363b003bcfd3
4
- data.tar.gz: 70b12365ae3f13cfca0fd70104d552efee6b520985e2844795a480130ae42929
3
+ metadata.gz: afe09cd2a6e4d3dea0a4ebe3548c86fb5a0ed25025e07b11565c4706a82267ef
4
+ data.tar.gz: ded746f6e623b489ea364623307b7589b2d3b7002cd2b051b0be28f26d701252
5
5
  SHA512:
6
- metadata.gz: 40937d99b2c3cc6044f6ddfda7ef3c8e789192e31e7ca47e5057f18c4deeb5b33a745bcef155a0c78bc35ce598f631e5d3a9bbbb18bb3b739d0aab7c4b87e2c7
7
- data.tar.gz: 4c7e83279bce76b4baa09d74b5b56394822405694fbcecc3b8674caedb9eef9e0baeea518305e1268ac39c719e411010c9d78c00ab1cccb9b0378763b0809873
6
+ metadata.gz: bbe91b347c1056af614f4290a52ec2f2eb3dc64883b30a129ea41d8353626835738fb8169b2081d56d5688f6910e584b1805403317ed4b29de7d6ee674ca8f9a
7
+ data.tar.gz: 1d6211ea820d958976201635bbfb239c53ab9a1818cc4779263ce6dda72aaaf669ae26751dd2abe5f82038bc21af7d6a6495b398e89bb4f9c1cc20b6cc876cb9
data/exe/aurora_fetch CHANGED
@@ -18,7 +18,7 @@ args = if uri.scheme == "telnet" || uri.scheme == "rfc2217"
18
18
  end
19
19
 
20
20
  client = ModBus::RTUClient.new(*args)
21
- client.debug = true
21
+ client.logger = Logger.new($stdout, :debug)
22
22
  slave = client.with_slave(1)
23
23
 
24
24
  registers = slave.holding_registers[ARGV[1].to_i]
data/exe/aurora_mock CHANGED
@@ -21,7 +21,7 @@ args = if uri.scheme == "telnet" || uri.scheme == "rfc2217"
21
21
  port = ARGV[1]&.to_i || 502
22
22
 
23
23
  server1 = ModBus::RTUServer.new(*args)
24
- server1.debug = true
24
+ server1.logger = Logger.new($stdout, :debug)
25
25
  # AID Tool queries slave 1, AWL queries slave 2; just use both
26
26
  slave1 = server1.with_slave(1)
27
27
  slave2 = server1.with_slave(2)
data/exe/aurora_monitor CHANGED
@@ -3,11 +3,39 @@
3
3
 
4
4
  require "aurora"
5
5
  require "ccutrer-serialport"
6
+ require "logger"
7
+ require "optparse"
6
8
  require "socket"
7
9
  require "uri"
8
10
 
11
+ diff_only = debug_modbus = ignore_awl_heartbeat = ignore_sensors = false
12
+
13
+ OptionParser.new do |opts|
14
+ opts.banner = "Usage: aurora_monitor /path/to/serial/port [options]"
15
+
16
+ opts.on("-q", "--quiet",
17
+ "Enables quiet mode (--diff-only, --ignore-awl-heartbeat, --ignore-sensors) to ease in deciphering new registers") do # rubocop:disable Layout/LineLength
18
+ diff_only = true
19
+ ignore_awl_heartbeat = true
20
+ ignore_sensors = true
21
+ end
22
+ opts.on("--diff-only", "Only show registers if they've changed from their previous value") { diff_only = true }
23
+ opts.on("--debug-modbus", "Print actual protocol bytes") { debug_modbus = true }
24
+ opts.on("--ignore-awl-heartbeat", "Don't print AWL heartbeat requests") { ignore_awl_heartbeat = true }
25
+ opts.on("--ignore-sensors", "Don't print sensor registers (i.e. because they change a lot)") { ignore_sensors = true }
26
+ opts.on("-h", "--help", "Prints this help") do
27
+ puts opts
28
+ exit
29
+ end
30
+ end.parse!
31
+
9
32
  uri = URI.parse(ARGV[0])
10
33
 
34
+ last_registers = {}
35
+
36
+ SENSOR_REGISTERS = [16, 19, 20, 740, 900, 1109, 1105, 1106, 1107, 1108, 1110, 1111, 1114, 1117, 1134, 1147, 1149, 1151,
37
+ 1153, 1165].freeze
38
+
11
39
  args = if uri.scheme == "telnet" || uri.scheme == "rfc2217"
12
40
  require "net/telnet/rfc2217"
13
41
  [Net::Telnet::RFC2217.new("Host" => uri.host,
@@ -20,23 +48,39 @@ args = if uri.scheme == "telnet" || uri.scheme == "rfc2217"
20
48
 
21
49
  server = ModBus::RTUServer.new(*args)
22
50
  server.promiscuous = true
23
- server.debug = true
51
+ server.logger = Logger.new($stdout)
52
+ server.logger.level = :debug if debug_modbus
53
+
54
+ diff_and_print = lambda do |registers|
55
+ registers = registers.slice(*(registers.keys - SENSOR_REGISTERS)) if ignore_sensors
56
+ next puts Aurora.print_registers(registers) unless diff_only
57
+
58
+ new_registers = last_registers.merge(registers)
59
+ diff = Aurora.diff_registers(last_registers, new_registers)
60
+ unless diff.empty?
61
+ puts "#{Time.now} ===== read"
62
+ puts Aurora.print_registers(diff)
63
+ end
64
+ last_registers = new_registers
65
+ end
24
66
 
25
67
  server.request_callback = lambda { |uid, func, req|
26
68
  if func == 68
27
- puts "===== no idea to #{uid}: #{req.inspect}"
69
+ puts "#{Time.now} ===== no idea to #{uid}: #{req.inspect}" unless diff_only
28
70
  elsif func == 67
29
- puts "===== write discontiguous registers to #{uid}:"
71
+ puts "#{Time.now} ===== write discontiguous registers to #{uid}:"
30
72
  registers = req.map { |p| [p[:addr], p[:val]] }.to_h
31
73
  puts Aurora.print_registers(registers)
32
74
  elsif func == 16
33
- puts "===== write multiple registers to #{uid}:"
34
75
  registers = Range.new(req[:addr], req[:addr] + req[:quant] - 1).zip(req[:val]).to_h
76
+ next if ignore_awl_heartbeat && registers == { 460 => 102, 461 => 0, 462 => 5 }
77
+
78
+ puts "#{Time.now} ===== write multiple registers to #{uid}:"
35
79
  puts Aurora.print_registers(registers)
36
80
  elsif [3, 65, 66].include?(func)
37
81
  # no output
38
82
  else
39
- puts "**** new func #{func}"
83
+ puts "#{Time.now} **** new func #{func}"
40
84
  end
41
85
  }
42
86
 
@@ -46,9 +90,9 @@ server.response_callback = lambda { |uid, func, res, req|
46
90
  puts "wrong number of results"
47
91
  next
48
92
  end
49
- puts "===== read registers from #{uid}"
93
+ puts "#{Time.now} ===== read registers from #{uid}" unless diff_only
50
94
  registers = Range.new(req[:addr], req[:addr] + req[:quant], true).to_a.zip(res).to_h
51
- puts Aurora.print_registers(registers)
95
+ diff_and_print.call(registers)
52
96
  elsif func == 65 && res.is_a?(Array) && req
53
97
  register_list = []
54
98
  req.each { |params| register_list.concat(Range.new(params[:addr], params[:addr] + params[:quant], true).to_a) }
@@ -56,21 +100,21 @@ server.response_callback = lambda { |uid, func, res, req|
56
100
  puts "wrong number of results"
57
101
  next
58
102
  end
59
- puts "===== read multiple register ranges from #{uid}"
103
+ puts "#{Time.now} ===== read multiple register ranges from #{uid}" unless diff_only
60
104
  result = register_list.zip(res).to_h
61
- puts Aurora.print_registers(result)
105
+ diff_and_print.call(result)
62
106
  elsif func == 66 && res.is_a?(Array) && req
63
107
  unless req.length == res.length
64
108
  puts "wrong number of results"
65
109
  next
66
110
  end
67
- puts "===== read discontiguous registers from #{uid}"
111
+ puts "#{Time.now} ===== read discontiguous registers from #{uid}" unless diff_only
68
112
  registers = req.zip(res).to_h
69
- puts Aurora.print_registers(registers)
113
+ diff_and_print.call(registers)
70
114
  elsif [16, 67, 68].include?(func)
71
115
  # no output
72
116
  else
73
- puts "**** new func #{func}"
117
+ puts "#{Time.now} **** new func #{func}"
74
118
  end
75
119
  }
76
120
 
@@ -29,7 +29,7 @@ class MQTTBridge
29
29
  @homie = homie
30
30
  @mutex = Mutex.new
31
31
 
32
- @homie.instance_variable_set(:@block, lambda do |topic, value|
32
+ @homie.out_of_band_topic_proc = lambda do |topic, value|
33
33
  @mutex.synchronize do
34
34
  case topic
35
35
  when /\$modbus$/
@@ -62,9 +62,10 @@ class MQTTBridge
62
62
  end
63
63
  end
64
64
  rescue StandardError => e
65
- puts "failed processing message: #{e}\n#{e.backtrace}"
66
- end)
65
+ warn "failed processing message: #{e}\n#{e.backtrace}"
66
+ end
67
67
 
68
+ @abc.refresh
68
69
  publish_basic_attributes
69
70
 
70
71
  loop do
@@ -75,6 +76,7 @@ class MQTTBridge
75
76
  current_mode
76
77
  dhw_water_temperature
77
78
  entering_air_temperature
79
+ entering_water_temperature
78
80
  fan_speed
79
81
  leaving_air_temperature
80
82
  leaving_water_temperature
@@ -91,15 +93,15 @@ class MQTTBridge
91
93
  @homie_abc[property.to_s.tr("_", "-")].value = @abc.public_send(property)
92
94
  end
93
95
 
94
- @abc.zones.each do |z|
95
- homie_zone = @homie["zone#{z.zone_number}"]
96
+ @abc.zones.each_with_index do |z, idx|
97
+ homie_zone = @homie["zone#{idx + 1}"]
96
98
  homie_zone.each do |property|
97
99
  property.value = z.public_send(property.id.tr("-", "_"))
98
100
  end
99
101
  end
100
102
  end
101
103
  rescue => e
102
- puts "got garbage: #{e}; #{e.backtrace}"
104
+ warn "got garbage: #{e}; #{e.backtrace}"
103
105
  exit 1
104
106
  end
105
107
  sleep(5)
@@ -134,11 +136,11 @@ class MQTTBridge
134
136
  end
135
137
  end
136
138
 
137
- @abc.zones.each do |zone|
139
+ @abc.zones.each_with_index do |zone, i|
138
140
  type = zone.is_a?(Aurora::IZ2Zone) ? "IntelliZone 2 Zone" : "Thermostat"
139
- @homie.node("zone#{zone.zone_number}", "Zone #{zone.zone_number}", type) do |node|
141
+ @homie.node("zone#{i + 1}", "Zone #{i + 1}", type) do |node|
140
142
  allowed_modes = %w[off auto cool heat]
141
- allowed_modes << "eheat" if !zone.respond_to?(:zone_number) || zone.zone_number == 1
143
+ allowed_modes << "eheat" if i.zero?
142
144
  node.property("target-mode", "Target Heating/Cooling Mode", :enum, zone.target_mode,
143
145
  format: allowed_modes) do |value, property|
144
146
  @mutex.synchronize { property.value = zone.target_mode = value.to_sym }
@@ -186,4 +188,13 @@ class MQTTBridge
186
188
  end
187
189
  end
188
190
 
189
- MQTTBridge.new(abc, MQTT::Homie::Device.new("aurora", "Aurora MQTT Bridge", mqtt: mqtt_uri))
191
+ log_level = ARGV.include?("--debug") ? :debug : :warn
192
+ logger = Logger.new($stdout)
193
+ logger.level = log_level
194
+ slave.logger = logger
195
+
196
+ device = "aurora-#{abc.serial_number}"
197
+ homie = MQTT::Homie::Device.new(device, "Aurora MQTT Bridge", mqtt: mqtt_uri)
198
+ homie.logger = logger
199
+
200
+ MQTTBridge.new(abc, homie)
@@ -3,6 +3,7 @@
3
3
  module Aurora
4
4
  class ABCClient
5
5
  attr_reader :modbus_slave,
6
+ :serial_number,
6
7
  :zones,
7
8
  :current_mode,
8
9
  :fan_speed,
@@ -27,6 +28,9 @@ module Aurora
27
28
  @modbus_slave = modbus_slave
28
29
  @modbus_slave.read_retry_timeout = 15
29
30
  @modbus_slave.read_retries = 2
31
+ registers_array = @modbus_slave.holding_registers[105...110]
32
+ registers = registers_array.each_with_index.map { |r, i| [i + 105, r] }.to_h
33
+ @serial_number = Aurora.transform_registers(registers)[105]
30
34
  iz2_zone_count = @modbus_slave.holding_registers[483]
31
35
  # TODO: better detect IZ2/Non-IZ2
32
36
  @zones = if iz2_zone_count > 1
@@ -48,7 +52,7 @@ module Aurora
48
52
  registers_to_read << base3
49
53
  end
50
54
  else
51
- registers_to_read << 745..747
55
+ registers_to_read << (745..747)
52
56
  end
53
57
 
54
58
  registers = @modbus_slave.holding_registers[*registers_to_read]
@@ -17,7 +17,7 @@ module Aurora
17
17
  end
18
18
 
19
19
  def holding_registers
20
- WFProxy.new(self, :holding_register)
20
+ @holding_registers ||= WFProxy.new(self, :holding_register)
21
21
  end
22
22
  end
23
23
 
@@ -33,7 +33,6 @@ module Aurora
33
33
  def read_rtu_response(io)
34
34
  # Read the slave_id and function code
35
35
  msg = read(io, 2)
36
- log logging_bytes(msg)
37
36
 
38
37
  function_code = msg.getbyte(1)
39
38
  case function_code
@@ -52,7 +52,6 @@ module Aurora
52
52
  end
53
53
 
54
54
  def to_string(registers, idx, length)
55
- puts "converting #{idx} of length #{length}"
56
55
  (idx...(idx + length)).map do |i|
57
56
  (registers[i] >> 8).chr + (registers[i] & 0xff).chr
58
57
  end.join.sub(/[ \0]+$/, "")
@@ -349,7 +348,7 @@ module Aurora
349
348
  ->(v) { from_bitmask(v, AXB_INPUTS) } => [1103],
350
349
  ->(v) { from_bitmask(v, AXB_OUTPUTS) } => [1104],
351
350
  ->(v) { TO_TENTHS.call(NEGATABLE.call(v)) } => [1136],
352
- ->(v) { HEATING_MODE[v] } => [12_602, 21_202, 21_211, 21_220, 21_229, 21_238, 21_247],
351
+ ->(v) { HEATING_MODE[v] } => [12_606, 21_202, 21_211, 21_220, 21_229, 21_238, 21_247],
353
352
  ->(v) { FAN_MODE[v] } => [12_621, 21_205, 21_214, 21_223, 21_232, 21_241, 21_250],
354
353
  ->(v) { from_bitmask(v, HUMIDIFIER_SETTINGS) } => [31_109],
355
354
  ->(v) { { humidification_target: v >> 8, dehumidification_target: v & 0xff } } => [31_110],
@@ -495,25 +494,6 @@ module Aurora
495
494
  61_000..61_009
496
495
  ].freeze
497
496
 
498
- def read_all_registers(modbus_slave)
499
- result = []
500
- REGISTER_RANGES.each do |range|
501
- # read at most 100 at a time
502
- range.each_slice(100) do |keys|
503
- result.concat(modbus_slave.holding_registers[keys.first..keys.last])
504
- end
505
- end
506
- REGISTER_RANGES.map(&:to_a).flatten.zip(result).to_h
507
- end
508
-
509
- def diff_registers(lhs, rhs)
510
- diff = {}
511
- lhs.each_key do |k|
512
- diff[k] = [lhs[k], rhs[k]] if lhs[k] != rhs[k]
513
- end
514
- diff
515
- end
516
-
517
497
  REGISTER_NAMES = {
518
498
  1 => "Random Start Delay",
519
499
  2 => "ABC Program Version",
@@ -610,7 +590,7 @@ module Aurora
610
590
  1153 => "Total Watts",
611
591
  1157 => "Ht of Rej",
612
592
  1165 => "VS Pump Watts",
613
- 12_602 => "Heating Mode (write)",
593
+ 12_606 => "Heating Mode (write)",
614
594
  12_619 => "Heating Setpoint (write)",
615
595
  12_620 => "Cooling Setpoint (write)",
616
596
  12_621 => "Fan Mode (write)",
@@ -648,6 +628,25 @@ module Aurora
648
628
  registers
649
629
  end
650
630
 
631
+ def read_all_registers(modbus_slave)
632
+ result = []
633
+ REGISTER_RANGES.each do |range|
634
+ # read at most 100 at a time
635
+ range.each_slice(100) do |keys|
636
+ result.concat(modbus_slave.holding_registers[keys.first..keys.last])
637
+ end
638
+ end
639
+ REGISTER_RANGES.map(&:to_a).flatten.zip(result).to_h
640
+ end
641
+
642
+ def diff_registers(lhs, rhs)
643
+ diff = {}
644
+ (lhs.keys | rhs.keys).each do |k|
645
+ diff[k] = rhs[k] if lhs[k] != rhs[k]
646
+ end
647
+ diff
648
+ end
649
+
651
650
  def print_registers(registers)
652
651
  result = []
653
652
  registers.each do |(k, value)|
@@ -655,15 +654,17 @@ module Aurora
655
654
  next if REGISTER_NAMES.key?(k) && REGISTER_NAMES[k].nil?
656
655
 
657
656
  name = REGISTER_NAMES[k]
657
+
658
658
  value_proc = REGISTER_CONVERTERS.find { |(_, z)| z.include?(k) }&.first || ->(v) { v }
659
659
  format = REGISTER_FORMATS.find { |(_, z)| z.include?(k) }&.first || "%s"
660
660
  format = "%1$d (0x%1$04x)" unless name
661
- name ||= "???"
662
661
 
663
662
  value = value_proc.arity == 2 ? value_proc.call(registers, k) : value_proc.call(value)
664
663
  value = value.join(", ") if value.is_a?(Array)
665
664
  value = format(format, value) if value
666
665
 
666
+ name ||= "???"
667
+
667
668
  result << "#{name} (#{k}): #{value}"
668
669
  end
669
670
  result.join("\n")
@@ -21,7 +21,7 @@ module Aurora
21
21
  def target_mode=(value)
22
22
  return unless (raw_value = HEATING_MODE.invert[value])
23
23
 
24
- @abc.modbus_slave.holding_registers[12_602] = raw_value
24
+ @abc.modbus_slave.holding_registers[12_606] = raw_value
25
25
  @target_mode = value
26
26
  end
27
27
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Aurora
4
- VERSION = "0.3.0"
4
+ VERSION = "0.3.4"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: waterfurnace_aurora
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.3.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Cody Cutrer
@@ -30,14 +30,14 @@ dependencies:
30
30
  requirements:
31
31
  - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: 1.4.2
33
+ version: 1.4.4
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
- version: 1.4.2
40
+ version: 1.4.4
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: net-telnet-rfc2217
43
43
  requirement: !ruby/object:Gem::Requirement
@@ -58,14 +58,14 @@ dependencies:
58
58
  requirements:
59
59
  - - "~>"
60
60
  - !ruby/object:Gem::Version
61
- version: '2.0'
61
+ version: '2.1'
62
62
  type: :runtime
63
63
  prerelease: false
64
64
  version_requirements: !ruby/object:Gem::Requirement
65
65
  requirements:
66
66
  - - "~>"
67
67
  - !ruby/object:Gem::Version
68
- version: '2.0'
68
+ version: '2.1'
69
69
  - !ruby/object:Gem::Dependency
70
70
  name: byebug
71
71
  requirement: !ruby/object:Gem::Requirement