waterfurnace_aurora 0.3.10 → 0.4.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: 9043266cc8a0382960f9ee29954fa9da2c33f8507c3d0075c58d9651618fa128
4
- data.tar.gz: 40520db833f13a4caea708589a334b21198ed551376ee70aff666c875257ea03
3
+ metadata.gz: 2d7a11b389793a826e3dc602b69ea7aea2ee73afc9839eb1b9df41f38895222e
4
+ data.tar.gz: c30fad75a07bded088cb3a961e88bf298f5c482eafcb681309693ae00b079de0
5
5
  SHA512:
6
- metadata.gz: b8db6bf0c4f1e19270792fd43d16e06dcb3a1ab357c8553d6cc39bb820f546ff3782472a3c8f7cde7d2323686d7712c2f2699d53b8cd984344bda02982a979bc
7
- data.tar.gz: 70d7fa1a21e3c3003f24f1cbe8e5909ceb216002e98d08e3262e9f56a3c2636af775a7e47b2422bbbe7511d8cf5ae390436b0d809f28a4e34f6c21ed609327a1
6
+ metadata.gz: 7b0d336748b78005248f5b9b64afb573345a6a0426346bb2d1053d3fdaa4a814a313e5f621ca632d02358f0ec3d2a0c4c2506cb4dada0551c344a595be1f944b
7
+ data.tar.gz: 0d46028195f215edfe1a1dcf0ec1b7ea50071ba1a7638ba9a2fd0bc3418cd4f451c88e7fb461c741970d683f94eef0c25ec5f2b64db4b80e1ae7ecc0b702a9f4
data/exe/aurora_fetch CHANGED
@@ -28,28 +28,10 @@ unless ARGV.length == 2
28
28
  exit 1
29
29
  end
30
30
 
31
- uri = URI.parse(ARGV[0])
32
-
33
- args = case uri.scheme
34
- when "tcp"
35
- require "socket"
36
- [TCPSocket.new(uri.host, uri.port)]
37
- when "telnet", "rfc2217"
38
- require "net/telnet/rfc2217"
39
- [Net::Telnet::RFC2217.new(uri.host,
40
- port: uri.port || 23,
41
- baud: 19_200,
42
- parity: :even)]
43
- else
44
- [CCutrer::SerialPort.new(uri.path, baud: 19_200, parity: :even)]
45
- end
46
-
47
- client = ModBus::RTUClient.new(*args)
48
- client.logger = Logger.new($stdout)
49
- client.logger.level = debug_modbus ? :debug : :warn
50
-
51
- slave = client.with_slave(1)
52
- abc = Aurora::ABCClient.new(slave)
31
+ abc = Aurora::ABCClient.new(ARGV[0])
32
+ abc.modbus_slave.logger = Logger.new($stdout)
33
+ abc.modbus_slave.logger.level = debug_modbus ? :debug : :warn
34
+
53
35
  registers = abc.query_registers(ARGV[1])
54
36
 
55
37
  if yaml
data/exe/aurora_mock CHANGED
@@ -2,29 +2,29 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require "aurora"
5
- require "ccutrer-serialport"
6
5
  require "uri"
7
6
  require "yaml"
8
7
 
9
8
  uri = URI.parse(ARGV[0])
10
9
 
11
- args = case uri.scheme
12
- when "tcp"
13
- require "socket"
14
- [TCPSocket.new(uri.host, uri.port)]
15
- when "telnet", "rfc2217"
16
- require "net/telnet/rfc2217"
17
- [Net::Telnet::RFC2217.new(uri.host,
18
- port: uri.port || 23,
19
- baud: 19_200,
20
- parity: :even)]
21
- else
22
- [CCutrer::SerialPort.new(uri.path, baud: 19_200, parity: :even)]
23
- end
10
+ io = case uri.scheme
11
+ when "tcp"
12
+ require "socket"
13
+ TCPSocket.new(uri.host, uri.port)
14
+ when "telnet", "rfc2217"
15
+ require "net/telnet/rfc2217"
16
+ Net::Telnet::RFC2217.new(uri.host,
17
+ port: uri.port || 23,
18
+ baud: 19_200,
19
+ parity: :even)
20
+ else
21
+ require "ccutrer-serialport"
22
+ CCutrer::SerialPort.new(uri.path, baud: 19_200, parity: :even)
23
+ end
24
24
 
25
25
  port = ARGV[1]&.to_i || 502
26
26
 
27
- server1 = ModBus::RTUServer.new(*args)
27
+ server1 = ModBus::RTUServer.new(io)
28
28
  server1.logger = Logger.new($stdout, :debug)
29
29
  # AID Tool queries slave 1, AWL queries slave 2; just use both
30
30
  slave1 = server1.with_slave(1)
@@ -35,7 +35,7 @@ slave255 = server2.with_slave(255)
35
35
  r = slave1.holding_registers = slave2.holding_registers = slave255.holding_registers = Array.new(31_473, 0)
36
36
 
37
37
  # prepopulate some data
38
- registers = YAML.safe_load(File.read(File.expand_path("registers.yml", __dir__)))
38
+ registers = YAML.safe_load(File.read(ARGV[2]))
39
39
  registers.each { |(k, v)| r[k] = v }
40
40
 
41
41
  server1.request_callback = lambda { |uid, func, req|
data/exe/aurora_monitor CHANGED
@@ -2,7 +2,6 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require "aurora"
5
- require "ccutrer-serialport"
6
5
  require "logger"
7
6
  require "optparse"
8
7
  require "socket"
@@ -10,7 +9,7 @@ require "uri"
10
9
 
11
10
  diff_only = debug_modbus = ignore_awl_heartbeat = ignore_sensors = false
12
11
 
13
- OptionParser.new do |opts|
12
+ options = OptionParser.new do |opts|
14
13
  opts.banner = "Usage: aurora_monitor /path/to/serial/port [options]"
15
14
 
16
15
  opts.on("-q", "--quiet",
@@ -27,7 +26,14 @@ OptionParser.new do |opts|
27
26
  puts opts
28
27
  exit
29
28
  end
30
- end.parse!
29
+ end
30
+
31
+ options.parse!
32
+
33
+ unless ARGV.length == 2
34
+ puts options
35
+ exit 1
36
+ end
31
37
 
32
38
  uri = URI.parse(ARGV[0])
33
39
 
@@ -36,21 +42,22 @@ last_registers = {}
36
42
  SENSOR_REGISTERS = [16, 19, 20, 740, 900, 1109, 1105, 1106, 1107, 1108, 1110, 1111, 1114, 1117, 1134, 1147, 1149, 1151,
37
43
  1153, 1165].freeze
38
44
 
39
- args = case uri.scheme
40
- when "tcp"
41
- require "socket"
42
- [TCPSocket.new(uri.host, uri.port)]
43
- when "telnet", "rfc2217"
44
- require "net/telnet/rfc2217"
45
- [Net::Telnet::RFC2217.new(uri.host,
46
- port: uri.port || 23,
47
- baud: 19_200,
48
- parity: :even)]
49
- else
50
- [CCutrer::SerialPort.new(uri.path, baud: 19_200, parity: :even)]
51
- end
52
-
53
- server = ModBus::RTUServer.new(*args)
45
+ io = case uri.scheme
46
+ when "tcp"
47
+ require "socket"
48
+ TCPSocket.new(uri.host, uri.port)
49
+ when "telnet", "rfc2217"
50
+ require "net/telnet/rfc2217"
51
+ Net::Telnet::RFC2217.new(uri.host,
52
+ port: uri.port || 23,
53
+ baud: 19_200,
54
+ parity: :even)
55
+ else
56
+ require "ccutrer-serialport"
57
+ CCutrer::SerialPort.new(uri.path, baud: 19_200, parity: :even)
58
+ end
59
+
60
+ server = ModBus::RTUServer.new(io)
54
61
  server.promiscuous = true
55
62
  server.logger = Logger.new($stdout)
56
63
  server.logger.level = debug_modbus ? :debug : :warn
@@ -77,7 +84,10 @@ server.request_callback = lambda { |uid, func, req|
77
84
  puts Aurora.print_registers(registers)
78
85
  elsif func == 16
79
86
  registers = Range.new(req[:addr], req[:addr] + req[:quant] - 1).zip(req[:val]).to_h
80
- next if ignore_awl_heartbeat && registers == { 460 => 102, 461 => 0, 462 => 5 }
87
+ if ignore_awl_heartbeat && [{ 460 => 102, 461 => 0, 462 => 5 },
88
+ { 460 => 102, 461 => 0, 462 => 1 }].include?(registers)
89
+ next
90
+ end
81
91
 
82
92
  puts "#{Time.now} ===== write multiple registers to #{uid}:"
83
93
  puts Aurora.print_registers(registers)
@@ -5,27 +5,34 @@ require "aurora"
5
5
  require "homie-mqtt"
6
6
  require "ccutrer-serialport"
7
7
  require "uri"
8
+ require "optparse"
9
+ require "yaml"
8
10
  require "aurora/core_ext/string"
9
11
 
10
- uri = URI.parse(ARGV[0])
12
+ debug_modbus = false
13
+
14
+ options = OptionParser.new do |opts|
15
+ opts.banner = "Usage: aurora_mqtt_bridge /path/to/serial/port [options]"
16
+
17
+ opts.on("--debug-modbus", "Print actual protocol bytes") { debug_modbus = true }
18
+ opts.on("-h", "--help", "Prints this help") do
19
+ puts opts
20
+ exit
21
+ end
22
+ end
23
+
24
+ options.parse!
25
+
26
+ unless ARGV.length == 2
27
+ puts options
28
+ exit 1
29
+ end
30
+
11
31
  mqtt_uri = ARGV[1]
12
32
 
13
- args = case uri.scheme
14
- when "tcp"
15
- require "socket"
16
- [TCPSocket.new(uri.host, uri.port)]
17
- when "telnet", "rfc2217"
18
- require "net/telnet/rfc2217"
19
- [Net::Telnet::RFC2217.new(uri.host,
20
- port: uri.port || 23,
21
- baud: 19_200,
22
- parity: :even)]
23
- else
24
- [CCutrer::SerialPort.new(uri.path, baud: 19_200, parity: :even)]
25
- end
26
-
27
- slave = ModBus::RTUClient.new(*args).with_slave(1)
28
- abc = Aurora::ABCClient.new(slave)
33
+ abc = Aurora::ABCClient.new(ARGV[0])
34
+ abc.modbus_slave.logger = Logger.new($stdout)
35
+ abc.modbus_slave.logger.level = debug_modbus ? :debug : :warn
29
36
 
30
37
  class MQTTBridge
31
38
  def initialize(abc, homie)
@@ -71,6 +78,12 @@ class MQTTBridge
71
78
  property.value = @abc.public_send(property.id.tr("-", "_"))
72
79
  end
73
80
 
81
+ if @abc.dhw?
82
+ @dhw["enabled"].value = @abc.dhw_enabled
83
+ @dhw["water-temperature"].value = @abc.dhw_water_temperature
84
+ @dhw["set-point"].value = @abc.dhw_setpoint
85
+ end
86
+
74
87
  @abc.faults.each_with_index do |fault_count, i|
75
88
  @faults["e#{i + 1}"].value = fault_count
76
89
  end
@@ -92,10 +105,10 @@ class MQTTBridge
92
105
 
93
106
  def publish_basic_attributes
94
107
  @homie_abc = @homie.node("abc", "Aurora Basic Control", "ABC") do |node|
95
- node.property("compressor-speed", "Compressor Speed", :integer, @abc.compressor_speed, format: 0..1)
108
+ node.property("compressor-speed", "Compressor Speed", :integer, @abc.compressor_speed,
109
+ format: @abc.vs_drive? ? 0..12 : 0..2)
96
110
  node.property("current-mode", "Current Heating/Cooling Mode", :enum, @abc.current_mode,
97
- format: %w[lockout standby blower h1 h2 c1 c2 eh1 eh2])
98
- node.property("dhw-water-temperature", "DHW Water Temperature", :float, @abc.dhw_water_temperature, unit: "ºF")
111
+ format: %w[lockout standby blower heating cooling eh1 eh2 emergency waiting dehumidify])
99
112
  node.property("entering-air-temperature", "Entering Air Temperature", :float, @abc.entering_air_temperature,
100
113
  unit: "ºF")
101
114
  node.property("entering-water-temperature", "Entering Water Temperature", :float,
@@ -129,6 +142,20 @@ class MQTTBridge
129
142
  end
130
143
  end
131
144
 
145
+ if @abc.dhw?
146
+ @dhw = @homie.node("dhw", "Domestic Hot Water Generator", "DHW") do |node|
147
+ node.property("enabled", "Enabled", :boolean, @abc.dhw_enabled) do |value, property|
148
+ @mutex.synchronize { property.value = @abc.dhw_enabled = value }
149
+ end
150
+ node.property("water-temperature", "DHW Water Temperature", :float,
151
+ @abc.dhw_water_temperature, unit: "ºF")
152
+ node.property("set-point", "DHW Set Point", :float, @abc.dhw_setpoint, format: 100..140,
153
+ unit: "ºF") do |value, property|
154
+ @mutex.synchronize { property.value = @abc.dhw_setpoint = value }
155
+ end
156
+ end
157
+ end
158
+
132
159
  @faults = @homie.node("faults", "Fault History", "ABC") do |node|
133
160
  @abc.faults.each_with_index do |count, i|
134
161
  name = Aurora::FAULTS[i + 1]
@@ -191,7 +218,7 @@ end
191
218
  log_level = ARGV.include?("--debug") ? :debug : :warn
192
219
  logger = Logger.new($stdout)
193
220
  logger.level = log_level
194
- slave.logger = logger
221
+ abc.modbus_slave.logger = logger
195
222
 
196
223
  device = "aurora-#{abc.serial_number}"
197
224
  homie = MQTT::Homie::Device.new(device, "Aurora MQTT Bridge", mqtt: mqtt_uri)
@@ -0,0 +1,21 @@
1
+ #!/bin/bash
2
+
3
+ mkdir -p html
4
+ mkdir -p html/css
5
+ mkdir -p html/js
6
+ mkdir -p html/images
7
+
8
+ IP=${1:-172.20.10.1}
9
+
10
+ curl http://$IP/ > html/index.htm
11
+ curl http://$IP/config.htm > html/config.htm
12
+ curl http://$IP/favicon.ico > html/favicon.ico
13
+ curl http://$IP/css/index.css > html/css/index.css
14
+ curl http://$IP/css/phone.css > html/css/phone.css
15
+ curl http://$IP/js/indexc.js > html/js/indexc.js
16
+ curl http://$IP/js/configc.js > html/js/configc.js
17
+ curl http://$IP/images/aurora.png > html/images/aurora.png
18
+ curl http://$IP/images/back.png > html/images/back.png
19
+ curl http://$IP/images/cfailed.png > html/images/cfailed.png
20
+ curl http://$IP/images/cgood.png > html/images/cgood.png
21
+ curl http://$IP/images/cidle.png > html/images/cidle.png
data/exe/web_aid_tool ADDED
@@ -0,0 +1,118 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require "aurora"
5
+ require "ccutrer-serialport"
6
+ require "logger"
7
+ require "optparse"
8
+ require "yaml"
9
+
10
+ debug_modbus = monitor = false
11
+
12
+ options = OptionParser.new do |opts|
13
+ opts.banner = "Usage: web_aid_tool /path/to/serial/port [options]"
14
+
15
+ opts.on("--debug-modbus", "Print actual protocol bytes") { debug_modbus = true }
16
+ opts.on("--monitor", "Print interpreted registers as they are requested, like aurora_monitor") { monitor = true }
17
+ opts.on("-h", "--help", "Prints this help") do
18
+ puts opts
19
+ exit
20
+ end
21
+ end
22
+
23
+ options.parse!
24
+
25
+ unless ARGV.length == 1
26
+ puts options
27
+ exit 1
28
+ end
29
+
30
+ slave = Aurora::ABCClient.open_modbus_slave(ARGV[0])
31
+ slave.logger = Logger.new($stdout)
32
+ slave.logger.level = debug_modbus ? :debug : :warn
33
+
34
+ def parse_query_string(query_string)
35
+ query_string.split("&").map { |p| p.split("=") }.to_h
36
+ end
37
+
38
+ # _don't_ do URI escaping
39
+ def encode_result(params)
40
+ params.map { |p| p.join("=") }.join("&")
41
+ end
42
+
43
+ require "sinatra"
44
+
45
+ set :public_folder, "html"
46
+ set :logging, false
47
+
48
+ get "/" do
49
+ send_file "html/index.htm"
50
+ end
51
+
52
+ units = 0
53
+
54
+ get "/getunits.cgi" do
55
+ encode_result(units: units)
56
+ end
57
+
58
+ get "/setunits.cgi" do
59
+ units = params["units"].to_i
60
+ encode_result(error: 0)
61
+ end
62
+
63
+ get "/config.cgi" do
64
+ encode_result(
65
+ "AWL Version" => Aurora::VERSION,
66
+ "Local Web Version" => 1.08,
67
+ "SSID" => nil,
68
+ "Units" => units,
69
+ "AWL ID" => ARGV[0],
70
+ "AWL ID CRC" => nil
71
+ )
72
+ end
73
+
74
+ get "/request.cgi" do
75
+ params = parse_query_string(request.query_string)
76
+ result = params.slice("cmd", "id", "set", "addr")
77
+ result["err"] = nil
78
+
79
+ # these are just aliases to get a certain set of registers
80
+ case params["cmd"]
81
+ when "abcinfo"
82
+ params["regs"] = "2;8;88,4"
83
+ when "devices"
84
+ params["regs"] = "800;803;806,3;812;815;818;824"
85
+ end
86
+
87
+ case params["cmd"]
88
+ when "getregs", "abcinfo", "devices"
89
+ queries = params["regs"].split(";").map do |range|
90
+ start, length = range.split(",").map(&:to_i)
91
+ next start if length.nil?
92
+
93
+ start...(start + length)
94
+ end
95
+ registers = slave.read_multiple_holding_registers(*queries)
96
+ if monitor
97
+ puts "READING"
98
+ puts Aurora.print_registers(registers)
99
+ end
100
+ result["values"] = registers.values.join(",")
101
+ when "putregs"
102
+ writes = params["regs"].split(";").map do |write|
103
+ write.split(",").map(&:to_i)
104
+ end.compact.to_h
105
+ if monitor
106
+ puts "WRITING"
107
+ puts Aurora.print_registers(writes)
108
+ end
109
+
110
+ writes.each do |(addr, value)|
111
+ slave.write_holding_register(addr, value)
112
+ end
113
+ else
114
+ return ""
115
+ end
116
+
117
+ encode_result(result)
118
+ end
@@ -1,13 +1,45 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "yaml"
4
+ require "uri"
5
+
3
6
  module Aurora
4
7
  class ABCClient
8
+ class << self
9
+ def open_modbus_slave(uri)
10
+ uri = URI.parse(uri)
11
+
12
+ io = case uri.scheme
13
+ when "tcp"
14
+ require "socket"
15
+ TCPSocket.new(uri.host, uri.port)
16
+ when "telnet", "rfc2217"
17
+ require "net/telnet/rfc2217"
18
+ Net::Telnet::RFC2217.new(uri.host,
19
+ port: uri.port || 23,
20
+ baud: 19_200,
21
+ parity: :even)
22
+
23
+ else
24
+ return Aurora::MockABC.new(YAML.load_file(uri.path)) if File.file?(uri.path)
25
+
26
+ require "ccutrer-serialport"
27
+ CCutrer::SerialPort.new(uri.path, baud: 19_200, parity: :even)
28
+ end
29
+
30
+ client = ::ModBus::RTUClient.new(io)
31
+ client.with_slave(1)
32
+ end
33
+ end
34
+
5
35
  attr_reader :modbus_slave,
6
36
  :serial_number,
7
37
  :zones,
8
38
  :faults,
9
39
  :current_mode,
10
40
  :fan_speed,
41
+ :dhw_enabled,
42
+ :dhw_setpoint,
11
43
  :entering_air_temperature,
12
44
  :relative_humidity,
13
45
  :leaving_air_temperature,
@@ -27,29 +59,33 @@ module Aurora
27
59
  :loop_pump_watts,
28
60
  :total_watts
29
61
 
30
- def initialize(modbus_slave)
31
- @modbus_slave = modbus_slave
62
+ def initialize(uri)
63
+ @modbus_slave = self.class.open_modbus_slave(uri)
32
64
  @modbus_slave.read_retry_timeout = 15
33
65
  @modbus_slave.read_retries = 2
34
- registers_array = @modbus_slave.holding_registers[105...110]
35
- registers = registers_array.each_with_index.map { |r, i| [i + 105, r] }.to_h
36
- @serial_number = Aurora.transform_registers(registers)[105]
66
+ registers = Aurora.transform_registers(@modbus_slave.holding_registers[88..91, 105...110, 1114])
67
+ @program = registers[88]
68
+ @serial_number = registers[105]
69
+ @dhw_water_temperature = registers[1114]
37
70
 
38
- @zones = if @modbus_slave.holding_registers[813].zero?
39
- [Thermostat.new(self)]
40
- else
71
+ @zones = if iz2?
41
72
  iz2_zone_count = @modbus_slave.holding_registers[483]
42
73
  (0...iz2_zone_count).map { |i| IZ2Zone.new(self, i + 1) }
74
+ else
75
+ [Thermostat.new(self)]
43
76
  end
44
77
  @faults = []
45
78
  end
46
79
 
47
80
  def query_registers(query)
81
+ implicit = false
48
82
  ranges = query.split(",").map do |addr|
49
83
  case addr
50
84
  when "known"
85
+ implicit = true
51
86
  Aurora::REGISTER_NAMES.keys
52
87
  when "valid"
88
+ implicit = true
53
89
  break Aurora::REGISTER_RANGES
54
90
  when /^(\d+)(?:\.\.|-)(\d+)$/
55
91
  $1.to_i..$2.to_i
@@ -61,13 +97,26 @@ module Aurora
61
97
  registers = {}
62
98
  queries.each do |subquery|
63
99
  registers.merge!(@modbus_slave.read_multiple_holding_registers(*subquery))
100
+ rescue ::ModBus::Errors::IllegalDataAddress
101
+ # maybe this unit doesn't respond to all the addresses we want?
102
+ raise unless implicit
103
+
104
+ # try each query individually
105
+ subquery.each do |subsubquery|
106
+ registers.merge!(@modbus_slave.read_multiple_holding_registers(subsubquery))
107
+ rescue ::ModBus::Errors::IllegalDataAddress
108
+ next
109
+ end
64
110
  end
65
111
  registers
66
112
  end
67
113
 
68
114
  def refresh
69
- registers_to_read = [19..20, 30, 340, 344, 347, 740..741, 900, 1110..1111, 1114, 1117, 1147..1153, 1165,
70
- 3027, 31_003]
115
+ registers_to_read = [6, 19..20, 25, 30, 340, 344, 347, 740..741, 900, 1110..1111, 1114, 1147..1153, 1165,
116
+ 31_003]
117
+ registers_to_read << (400..401) if dhw?
118
+ registers_to_read.concat([362, 3001]) if vs_drive?
119
+
71
120
  if zones.first.is_a?(IZ2Zone)
72
121
  zones.each_with_index do |_z, i|
73
122
  base1 = 21_203 + i * 9
@@ -78,7 +127,8 @@ module Aurora
78
127
  registers_to_read << base3
79
128
  end
80
129
  else
81
- registers_to_read << (745..747)
130
+ registers_to_read << 502
131
+ registers_to_read << (745..746)
82
132
  end
83
133
 
84
134
  @faults = @modbus_slave.holding_registers[601..699]
@@ -86,7 +136,11 @@ module Aurora
86
136
  registers = @modbus_slave.holding_registers[*registers_to_read]
87
137
  Aurora.transform_registers(registers)
88
138
 
139
+ outputs = registers[30]
140
+
89
141
  @fan_speed = registers[344]
142
+ @dhw_enabled = registers[400]
143
+ @dhw_setpoint = registers[401]
90
144
  @entering_air_temperature = registers[740]
91
145
  @relative_humidity = registers[741]
92
146
  @leaving_air_temperature = registers[900]
@@ -94,11 +148,22 @@ module Aurora
94
148
  @entering_water_temperature = registers[1111]
95
149
  @dhw_water_temperature = registers[1114]
96
150
  @waterflow = registers[1117]
97
- @compressor_speed = registers[3027]
151
+ @compressor_speed = if vs_drive?
152
+ registers[3001]
153
+ elsif outputs.include?(:cc2)
154
+ 2
155
+ elsif outputs.include?(:cc)
156
+ 1
157
+ else
158
+ 0
159
+ end
98
160
  @outdoor_temperature = registers[31_003]
99
161
  @fp1 = registers[19]
100
162
  @fp2 = registers[20]
101
- @locked_out = registers[1117]
163
+ @locked_out = registers[25] & 0x8000
164
+ @error = registers[25] & 0x7fff
165
+ @derated = (41..46).include?(@error)
166
+ @safe_mode = [47, 48, 49, 72, 74].include?(@error)
102
167
  @blower_only_ecm_speed = registers[340]
103
168
  @aux_heat_ecm_speed = registers[347]
104
169
  @compressor_watts = registers[1147]
@@ -107,19 +172,20 @@ module Aurora
107
172
  @loop_pump_watts = registers[1165]
108
173
  @total_watts = registers[1153]
109
174
 
110
- outputs = registers[30]
111
175
  @current_mode = if outputs.include?(:lockout)
112
176
  :lockout
113
- elsif outputs.include?(:cc2)
114
- outputs.include?(:rv) ? :c2 : :h2
115
- elsif outputs.include?(:cc)
116
- outputs.include?(:rv) ? :c1 : :h1
177
+ elsif registers[362]
178
+ :dehumidify
179
+ elsif outputs.include?(:cc2) || outputs.include?(:cc)
180
+ outputs.include?(:rv) ? :cooling : :heating
117
181
  elsif outputs.include?(:eh2)
118
- :eh2
182
+ outputs.include?(:rv) ? :eh2 : :emergency
119
183
  elsif outputs.include?(:eh1)
120
- :eh1
184
+ outputs.include?(:rv) ? :eh1 : :emergency
121
185
  elsif outputs.include?(:blower)
122
186
  :blower
187
+ elsif registers[6]
188
+ :waiting
123
189
  else
124
190
  :standby
125
191
  end
@@ -141,6 +207,101 @@ module Aurora
141
207
  @modbus_slave.holding_registers[347] = value
142
208
  end
143
209
 
210
+ def cooling_airflow_adjustment=(value)
211
+ value = 0x10000 + value if value.negative?
212
+ @modbus_slave.holding_registers[346] = value
213
+ end
214
+
215
+ def dhw_enabled=(value)
216
+ @modbus_slave.holding_registers[400] = value ? 1 : 0
217
+ end
218
+
219
+ def dhw_setpoint=(value)
220
+ raise ArgumentError unless (100..140).include?(value)
221
+
222
+ @modbus_slave.holding_registers[401] = (value * 10).to_i
223
+ end
224
+
225
+ def loop_pressure_trip=(value)
226
+ @modbus_slave.holding_registers[419] = (value * 10).to_i
227
+ end
228
+
229
+ def vs_pump_control=(value)
230
+ raise ArgumentError unless (value = VS_PUMP_CONTROL.invert[value])
231
+
232
+ @modbus_slave.holding_registers[323] = value
233
+ end
234
+
235
+ def vs_pump_min=(value)
236
+ @modbus_slave.holding_registers[321] = value
237
+ end
238
+
239
+ def vs_pump_max=(value)
240
+ @modbus_slave.holding_registers[322] = value
241
+ end
242
+
243
+ def line_voltage=(value)
244
+ raise ArgumentError unless (90..635).include?(value)
245
+
246
+ @modbus_slave.holding_registers[112] = value
247
+ end
248
+
249
+ def clear_fault_history
250
+ @modbus_slave.holding_registers[47] = 0x5555
251
+ end
252
+
253
+ def manual_operation(mode: :off,
254
+ compressor_speed: 0,
255
+ blower_speed: :with_compressor,
256
+ pump_speed: :with_compressor,
257
+ aux_heat: false)
258
+ raise ArgumentError, "mode must be :off, :heating, or :cooling" unless %i[off heating cooling].include?(mode)
259
+ raise ArgumentError, "compressor speed must be between 0 and 12" unless (0..12).include?(compressor_speed)
260
+
261
+ unless blower_speed == :with_compressor || (0..12).include?(blower_speed)
262
+ raise ArgumentError,
263
+ "blower speed must be :with_compressor or between 0 and 12"
264
+ end
265
+ unless pump_speed == :with_compressor || (0..100).include?(pump_speed)
266
+ raise ArgumentError,
267
+ "pump speed must be :with_compressor or between 0 and 100"
268
+ end
269
+
270
+ value = 0
271
+ value = 0x7fff if mode == :off
272
+ value |= 0x100 if mode == :cooling
273
+ value |= blower_speed == :with_compressor ? 0xf0 : (blower_speed << 4)
274
+ value |= 0x200 if aux_heat
275
+
276
+ @modbus_slave.holding_registers[3002] = value
277
+ @modbus_slave.holding_registers[323] = pump_speed == :with_compressor ? 0x7fff : pump_speed
278
+ end
279
+
280
+ def vs_drive?
281
+ @program == "ABCVSP"
282
+ end
283
+
284
+ def dhw?
285
+ (-999..999).include?(dhw_water_temperature)
286
+ end
287
+
288
+ # config aurora system
289
+ { thermostat: 800, axb: 806, iz2: 812, aoc: 815, moc: 818, eev2: 824 }.each do |(component, register)|
290
+ class_eval <<-RUBY, __FILE__, __LINE__ + 1
291
+ def #{component}?
292
+ @modbus_slave.holding_registers[#{register}] != 3
293
+ end
294
+
295
+ def add_#{component}
296
+ @modbus_slave.holding_registers[#{register}] = 2
297
+ end
298
+
299
+ def remove_#{component}
300
+ @modbus_slave.holding_registers[#{register}] = 3
301
+ end
302
+ RUBY
303
+ end
304
+
144
305
  def inspect
145
306
  "#<Aurora::ABCClient #{(instance_variables - [:@modbus_slave]).map do |iv|
146
307
  "#{iv}=#{instance_variable_get(iv).inspect}"