waterfurnace_aurora 0.3.13 → 0.4.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: a92c5ae13a08cf060e3c7f23613cc1302e20af0bc4bc7c77c610936bea48c2b5
4
- data.tar.gz: 914c7c6371755e06524e063b5eb6820ca63f67844713d4d396cd08559fa62cc6
3
+ metadata.gz: 2d7a11b389793a826e3dc602b69ea7aea2ee73afc9839eb1b9df41f38895222e
4
+ data.tar.gz: c30fad75a07bded088cb3a961e88bf298f5c482eafcb681309693ae00b079de0
5
5
  SHA512:
6
- metadata.gz: 384657c10727cf0a955cf4a4db5270d8ee021629e40c0dee65170e22206e3b0c21c85da2beade4f02143cdd5ff8d5631d5429c6a2957b96d933a3476cf83078c
7
- data.tar.gz: 6ea98c0cd77e83158d8313a851b0fa155cc6ee7a042aa338abb27c3c57cec543017b1ab97953aedf11d4645fedd5a6423c35b2c4f75fae6c768cfd1d5e47635d
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
@@ -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)
data/exe/web_aid_tool CHANGED
@@ -7,17 +7,13 @@ require "logger"
7
7
  require "optparse"
8
8
  require "yaml"
9
9
 
10
- debug_modbus = monitor = mock = false
10
+ debug_modbus = monitor = 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
- opts.on("--mock",
17
- "Instead of talking to an actual heat pump, mock it with registers from the given YAML file (instead of a serial port)") do # rubocop:disable Layout/LineLength
18
- mock = true
19
- end
20
- opts.on("--monitor", "Print interperted registers as they are requested, like aurora_monitor") { monitor = true }
16
+ opts.on("--monitor", "Print interpreted registers as they are requested, like aurora_monitor") { monitor = true }
21
17
  opts.on("-h", "--help", "Prints this help") do
22
18
  puts opts
23
19
  exit
@@ -31,51 +27,9 @@ unless ARGV.length == 1
31
27
  exit 1
32
28
  end
33
29
 
34
- class MockSlave
35
- def initialize(registers)
36
- @registers = registers
37
- end
38
-
39
- def read_multiple_holding_registers(*queries)
40
- result = {}
41
- queries.each do |query|
42
- Array(query).each do |i|
43
- result[i] = @registers[i]
44
- end
45
- end
46
- result
47
- end
48
-
49
- def write_holding_register(addr, value)
50
- @registers[addr] = value
51
- end
52
- end
53
-
54
- if mock
55
- slave = MockSlave.new(YAML.load_file(ARGV[0]))
56
- else
57
- uri = URI.parse(ARGV[0])
58
-
59
- args = case uri.scheme
60
- when "tcp"
61
- require "socket"
62
- [TCPSocket.new(uri.host, uri.port)]
63
- when "telnet", "rfc2217"
64
- require "net/telnet/rfc2217"
65
- [Net::Telnet::RFC2217.new(uri.host,
66
- port: uri.port || 23,
67
- baud: 19_200,
68
- parity: :even)]
69
- else
70
- [CCutrer::SerialPort.new(uri.path, baud: 19_200, parity: :even)]
71
- end
72
-
73
- client = ModBus::RTUClient.new(*args)
74
- client.logger = Logger.new($stdout)
75
- client.logger.level = debug_modbus ? :debug : :warn
76
-
77
- slave = client.with_slave(1)
78
- end
30
+ slave = Aurora::ABCClient.open_modbus_slave(ARGV[0])
31
+ slave.logger = Logger.new($stdout)
32
+ slave.logger.level = debug_modbus ? :debug : :warn
79
33
 
80
34
  def parse_query_string(query_string)
81
35
  query_string.split("&").map { |p| p.split("=") }.to_h
@@ -89,6 +43,7 @@ end
89
43
  require "sinatra"
90
44
 
91
45
  set :public_folder, "html"
46
+ set :logging, false
92
47
 
93
48
  get "/" do
94
49
  send_file "html/index.htm"
@@ -138,7 +93,10 @@ get "/request.cgi" do
138
93
  start...(start + length)
139
94
  end
140
95
  registers = slave.read_multiple_holding_registers(*queries)
141
- puts Aurora.print_registers(registers) if monitor
96
+ if monitor
97
+ puts "READING"
98
+ puts Aurora.print_registers(registers)
99
+ end
142
100
  result["values"] = registers.values.join(",")
143
101
  when "putregs"
144
102
  writes = params["regs"].split(";").map do |write|
@@ -147,7 +105,6 @@ get "/request.cgi" do
147
105
  if monitor
148
106
  puts "WRITING"
149
107
  puts Aurora.print_registers(writes)
150
- puts "==="
151
108
  end
152
109
 
153
110
  writes.each do |(addr, value)|
@@ -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,13 +59,14 @@ 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
71
  @zones = if iz2?
39
72
  iz2_zone_count = @modbus_slave.holding_registers[483]
@@ -79,8 +112,11 @@ module Aurora
79
112
  end
80
113
 
81
114
  def refresh
82
- registers_to_read = [6, 19..20, 25, 30, 340, 344, 347, 362, 740..741, 900, 1110..1111, 1114, 1147..1153, 1165,
83
- 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
+
84
120
  if zones.first.is_a?(IZ2Zone)
85
121
  zones.each_with_index do |_z, i|
86
122
  base1 = 21_203 + i * 9
@@ -100,7 +136,11 @@ module Aurora
100
136
  registers = @modbus_slave.holding_registers[*registers_to_read]
101
137
  Aurora.transform_registers(registers)
102
138
 
139
+ outputs = registers[30]
140
+
103
141
  @fan_speed = registers[344]
142
+ @dhw_enabled = registers[400]
143
+ @dhw_setpoint = registers[401]
104
144
  @entering_air_temperature = registers[740]
105
145
  @relative_humidity = registers[741]
106
146
  @leaving_air_temperature = registers[900]
@@ -108,7 +148,15 @@ module Aurora
108
148
  @entering_water_temperature = registers[1111]
109
149
  @dhw_water_temperature = registers[1114]
110
150
  @waterflow = registers[1117]
111
- @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
112
160
  @outdoor_temperature = registers[31_003]
113
161
  @fp1 = registers[19]
114
162
  @fp2 = registers[20]
@@ -124,15 +172,12 @@ module Aurora
124
172
  @loop_pump_watts = registers[1165]
125
173
  @total_watts = registers[1153]
126
174
 
127
- outputs = registers[30]
128
175
  @current_mode = if outputs.include?(:lockout)
129
176
  :lockout
130
177
  elsif registers[362]
131
178
  :dehumidify
132
- elsif outputs.include?(:cc2)
133
- outputs.include?(:rv) ? :c2 : :h2
134
- elsif outputs.include?(:cc)
135
- outputs.include?(:rv) ? :c1 : :h1
179
+ elsif outputs.include?(:cc2) || outputs.include?(:cc)
180
+ outputs.include?(:rv) ? :cooling : :heating
136
181
  elsif outputs.include?(:eh2)
137
182
  outputs.include?(:rv) ? :eh2 : :emergency
138
183
  elsif outputs.include?(:eh1)
@@ -172,7 +217,9 @@ module Aurora
172
217
  end
173
218
 
174
219
  def dhw_setpoint=(value)
175
- @modbus_slave.holding_registers[401] = value
220
+ raise ArgumentError unless (100..140).include?(value)
221
+
222
+ @modbus_slave.holding_registers[401] = (value * 10).to_i
176
223
  end
177
224
 
178
225
  def loop_pressure_trip=(value)
@@ -193,6 +240,12 @@ module Aurora
193
240
  @modbus_slave.holding_registers[322] = value
194
241
  end
195
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
+
196
249
  def clear_fault_history
197
250
  @modbus_slave.holding_registers[47] = 0x5555
198
251
  end
@@ -224,6 +277,14 @@ module Aurora
224
277
  @modbus_slave.holding_registers[323] = pump_speed == :with_compressor ? 0x7fff : pump_speed
225
278
  end
226
279
 
280
+ def vs_drive?
281
+ @program == "ABCVSP"
282
+ end
283
+
284
+ def dhw?
285
+ (-999..999).include?(dhw_water_temperature)
286
+ end
287
+
227
288
  # config aurora system
228
289
  { thermostat: 800, axb: 806, iz2: 812, aoc: 815, moc: 818, eev2: 824 }.each do |(component, register)|
229
290
  class_eval <<-RUBY, __FILE__, __LINE__ + 1
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Aurora
4
+ class MockABC
5
+ attr_accessor :logger
6
+
7
+ def initialize(registers)
8
+ @registers = registers
9
+ end
10
+
11
+ def read_retry_timeout=(_); end
12
+
13
+ def read_retries=(_); end
14
+
15
+ def holding_registers
16
+ self
17
+ end
18
+
19
+ def [](*register)
20
+ if register.length == 1
21
+ case register.first
22
+ when Integer
23
+ @registers[register.first]
24
+ when Range
25
+ @registers.values_at(*register.first.to_a)
26
+ else
27
+ raise ArgumentError, "Not implemented yet #{register.inspect}"
28
+ end
29
+ else
30
+ read_multiple_holding_registers(*register)
31
+ end
32
+ end
33
+
34
+ def read_multiple_holding_registers(*queries)
35
+ result = {}
36
+ queries.each do |query|
37
+ Array(query).each do |i|
38
+ result[i] = @registers[i]
39
+ end
40
+ end
41
+ result
42
+ end
43
+
44
+ def write_holding_register(addr, value)
45
+ @registers[addr] = value
46
+ end
47
+ end
48
+ end
@@ -142,6 +142,45 @@ module Aurora
142
142
  5 => :dirty_filter
143
143
  }.freeze
144
144
 
145
+ BRINE_TYPE = Hash.new("Water").merge!(
146
+ 485 => "Antifreeze"
147
+ ).freeze
148
+
149
+ FLOW_METER_TYPE = Hash.new("Other").merge!(
150
+ 0 => "None",
151
+ 1 => '3/4"',
152
+ 2 => '1"'
153
+ ).freeze
154
+
155
+ PUMP_TYPE = Hash.new("Other").merge!(
156
+ 0 => "Open Loop",
157
+ 1 => "FC1",
158
+ 2 => "FC2",
159
+ 3 => "VS Pump",
160
+ 4 => "VS Pump + 26-99",
161
+ 5 => "VS Pump + UPS26-99",
162
+ 6 => "FC1_GLNP",
163
+ 7 => "FC2_GLNP"
164
+ ).freeze
165
+
166
+ PHASE_TYPE = Hash.new("Other").merge!(
167
+ 0 => "Single",
168
+ 1 => "Three"
169
+ ).freeze
170
+
171
+ BLOWER_TYPE = Hash.new("Other").merge!(
172
+ 0 => "PSC",
173
+ 1 => "ECM 208/230",
174
+ 2 => "ECM 265/277",
175
+ 3 => "5 Speed ECM 460"
176
+ ).freeze
177
+
178
+ ENERGY_MONITOR_TYPE = Hash.new("Other").merge!(
179
+ 0 => "None",
180
+ 1 => "Compressor Monitor",
181
+ 2 => "Energy Monitor"
182
+ ).freeze
183
+
145
184
  VS_FAULTS = {
146
185
  "Under-Voltage Warning" => (71..77),
147
186
  "RPM Sensor Signal Fault" => (78..82),
@@ -157,8 +196,8 @@ module Aurora
157
196
 
158
197
  AR_SETTINGS = {
159
198
  0 => "Cycle with Compressor",
160
- 1 => "Cycle with Thermostat Humidification Call",
161
- 2 => "Slow Opening Water Valve",
199
+ 1 => "Slow Opening Water Valve",
200
+ 2 => "Cycle with Thermostat Humidification Call",
162
201
  3 => "Cycle with Blower"
163
202
  }.freeze
164
203
 
@@ -169,17 +208,18 @@ module Aurora
169
208
  fp1: value & 0x01 == 0x01 ? "30ºF" : "15ºF",
170
209
  fp2: value & 0x02 == 0x02 ? "30ºF" : "15ºF",
171
210
  rv: value & 0x04 == 0x04 ? "O" : "B",
172
- ar: AR_SETTINGS[(value >> 3) & 0x7],
211
+ ar: AR_SETTINGS[(value >> 3) & 0x3],
173
212
  cc: value & 0x20 == 0x20 ? "Single Stage" : "Dual Stage",
174
213
  lo: value & 0x40 == 0x40 ? "Continouous" : "Pulse",
175
- dh_rh: value & 0x80 == 0x80 ? "Dehumdifier On" : "Reheat On"
214
+ dh_rh: value & 0x80 == 0x80 ? "Dehumidifier On" : "Reheat On"
176
215
  }
177
216
  end
178
217
 
179
218
  COMPONENT_STATUS = {
180
219
  1 => :active,
181
220
  2 => :added,
182
- 3 => :removed
221
+ 3 => :removed,
222
+ 0xffff => :missing
183
223
  }.freeze
184
224
 
185
225
  SYSTEM_OUTPUTS = {
@@ -202,14 +242,14 @@ module Aurora
202
242
  # Off
203
243
 
204
244
  SYSTEM_INPUTS = {
205
- 0x01 => "Y1",
206
- 0x02 => "Y2",
207
- 0x04 => "W",
208
- 0x08 => "O",
209
- 0x10 => "G",
210
- 0x20 => "Dehumidifer",
211
- 0x40 => "Emergency Shutdown",
212
- 0x200 => "Load Shed"
245
+ 0x01 => :y1,
246
+ 0x02 => :y2,
247
+ 0x04 => :w,
248
+ 0x08 => :o,
249
+ 0x10 => :g,
250
+ 0x20 => :dh_rh,
251
+ 0x40 => :emergency_shutdown,
252
+ 0x200 => :load_shed
213
253
  }.freeze
214
254
 
215
255
  def status(value)
@@ -217,9 +257,10 @@ module Aurora
217
257
  lps: value & 0x80 == 0x80 ? :closed : :open,
218
258
  hps: value & 0x100 == 0x100 ? :closed : :open
219
259
  }
220
- result[:load_shed] = true if value & 0x0200 == 0x0200
221
- result[:emergency_shutdown] = true if value & 0x0040 == 0x0040
222
- leftover = value & ~0x03c0
260
+ SYSTEM_INPUTS.each do |(i, name)|
261
+ result[name] = true if value & i == i
262
+ end
263
+ leftover = value & ~0x03ff
223
264
  result[:unknown] = format("0x%04x", leftover) unless leftover.zero?
224
265
  result
225
266
  end
@@ -266,13 +307,36 @@ module Aurora
266
307
 
267
308
  }.freeze
268
309
 
269
- AXB_INPUTS = {
270
- }.freeze
310
+ def axb_inputs(value)
311
+ result = {}
312
+ result["Smart Grid"] = value & 0x001 == 0x001
313
+ result["Home Automation 1"] = value & 0x002 == 0x002
314
+ result["Home Automation 2"] = value & 0x004 == 0x004
315
+ result["Pump Slave"] = value & 0x008 == 0x008
316
+
317
+ result[:mb_address] = value & 0x010 == 0x010 ? 3 : 4
318
+ result[:sw1_2] = value & 0x020 == 0x020 # future use # rubocop:disable Naming/VariableNumber
319
+ result[:sw1_3] = value & 0x040 == 0x040 # future use # rubocop:disable Naming/VariableNumber
320
+ result[:cycle_with] = if value & 0x080 == 0x080 && value & 0x100 == 0x100
321
+ "Blower"
322
+ elsif value & 0x100 == 0x100
323
+ "Low Capacity Compressor"
324
+ elsif value & 0x080 == 0x080
325
+ "High Capacity Compressor"
326
+ else
327
+ "Thermostat Dehumidifier"
328
+ end
329
+ leftover = value & ~0x1ff
330
+ result[:unknown] = format("0x%04x", leftover) unless leftover.zero?
331
+ result
332
+ end
271
333
 
272
334
  AXB_OUTPUTS = {
273
- 0x10 => "Accessory 2",
274
- 0x02 => "Loop Pump",
275
- 0x01 => "DHW"
335
+ 0x01 => :dhw,
336
+ 0x02 => :loop_pump,
337
+ 0x04 => :diverting_valve,
338
+ 0x08 => :dehumidifer_reheat,
339
+ 0x10 => :accessory2
276
340
  }.freeze
277
341
 
278
342
  HEATING_MODE = {
@@ -338,6 +402,12 @@ module Aurora
338
402
  value
339
403
  end
340
404
 
405
+ def thermostat_override(value)
406
+ return [:off] if value == 0x7fff
407
+
408
+ from_bitmask(value, SYSTEM_INPUTS)
409
+ end
410
+
341
411
  def iz2_demand(value)
342
412
  {
343
413
  fan_demand: value >> 8,
@@ -411,11 +481,11 @@ module Aurora
411
481
  # intermittent off time allowed: 5, 10, 15, 20, 25, 30, 35, 40
412
482
 
413
483
  REGISTER_CONVERTERS = {
414
- TO_HUNDREDTHS => [2, 3, 801, 807, 813, 816, 817, 819, 820, 825, 828],
484
+ TO_HUNDREDTHS => [2, 3, 417, 418, 801, 804, 807, 813, 816, 817, 819, 820, 825, 828],
415
485
  method(:dipswitch_settings) => [4, 33],
416
486
  TO_TENTHS => [19, 20, 401, 419, 501, 502, 567, 740, 745, 746, 747, 900,
417
- 1105, 1106, 1107, 1108, 1110, 1111, 1114, 1117, 1134, 1136,
418
- 3327, 3522,
487
+ 1105, 1106, 1107, 1108, 1109, 1110, 1111, 1112, 1113, 1114, 1115, 1116, 1117, 1119, 1124, 1125, 1134,
488
+ 3322, 3323, 3325, 3326, 3327, 3330, 3522, 3903, 3905, 3906,
419
489
  12_619, 12_620,
420
490
  21_203, 21_204,
421
491
  21_212, 21_213,
@@ -429,6 +499,7 @@ module Aurora
429
499
  ->(v) { from_bitmask(v, SYSTEM_OUTPUTS) } => [27, 30],
430
500
  ->(v) { from_bitmask(v, SYSTEM_INPUTS) } => [28],
431
501
  method(:status) => [31],
502
+ method(:thermostat_override) => [32],
432
503
  ->(v) { !v.zero? } => [45, 362, 400],
433
504
  ->(registers, idx) { to_string(registers, idx, 4) } => [88],
434
505
  ->(registers, idx) { to_string(registers, idx, 12) } => [92],
@@ -440,17 +511,23 @@ module Aurora
440
511
  ->(v) { from_bitmask(v, VS_EEV2) } => [280, 3804],
441
512
  method(:vs_manual_control) => [323],
442
513
  NEGATABLE => [346, 1146],
514
+ ->(v) { BRINE_TYPE[v] } => [402],
515
+ ->(v) { FLOW_METER_TYPE[v] } => [403],
516
+ ->(v) { BLOWER_TYPE[v] } => [404],
443
517
  ->(v) { v.zero? ? :closed : :open } => [405, 408, 410],
444
518
  ->(v) { SMARTGRID_ACTION[v] } => [406],
445
519
  ->(v) { HA_ALARM[v] } => [409, 411],
520
+ ->(v) { ENERGY_MONITOR_TYPE[v] } => [412],
521
+ ->(v) { PUMP_TYPE[v] } => [413],
522
+ ->(v) { PHASE_TYPE[v] } => [416],
446
523
  method(:iz2_fan_desired) => [565],
447
524
  ->(registers, idx) { to_string(registers, idx, 8) } => [710],
448
- ->(v) { COMPONENT_STATUS[v] } => [800, 806, 812, 815, 818, 824, 827],
449
- ->(v) { from_bitmask(v, AXB_INPUTS) } => [1103],
525
+ ->(v) { COMPONENT_STATUS[v] } => [800, 803, 806, 812, 815, 818, 824, 827],
526
+ method(:axb_inputs) => [1103],
450
527
  ->(v) { from_bitmask(v, AXB_OUTPUTS) } => [1104],
451
- ->(v) { TO_TENTHS.call(NEGATABLE.call(v)) } => [1136],
528
+ ->(v) { TO_TENTHS.call(NEGATABLE.call(v)) } => [1135, 1136],
529
+ method(:to_int32) => [1146, 1148, 1150, 1152, 1154, 1156, 1164, 3422, 3424],
452
530
  method(:manual_operation) => [3002],
453
- method(:to_int32) => [3422, 3424],
454
531
  ->(v) { HEATING_MODE[v] } => [12_606, 21_202, 21_211, 21_220, 21_229, 21_238, 21_247],
455
532
  ->(v) { FAN_MODE[v] } => [12_621, 21_205, 21_214, 21_223, 21_232, 21_241, 21_250],
456
533
  ->(v) { from_bitmask(v, HUMIDIFIER_SETTINGS) } => [31_109],
@@ -468,10 +545,11 @@ module Aurora
468
545
  }.freeze
469
546
 
470
547
  REGISTER_FORMATS = {
471
- "%ds" => [1, 6, 9, 15, 84, 85],
548
+ "%ds" => [1, 6, 9, 15, 84, 85, 110],
472
549
  "%dV" => [16, 112, 3331, 3424, 3523],
473
- "%0.1fºF" => [19, 20, 401, 501, 502, 567, 740, 745, 746, 747, 900, 1110, 1111, 1114, 1134, 1136,
474
- 3327, 3522,
550
+ "%0.1fºF" => [19, 20, 401, 501, 502, 567, 740, 745, 746, 747, 900,
551
+ 1109, 1110, 1111, 1112, 1113, 1114, 1124, 1125, 1134, 1135, 1136,
552
+ 3325, 3326, 3327, 3330, 3522, 3903, 3905, 3906,
475
553
  12_619, 12_620,
476
554
  21_203, 21_204,
477
555
  21_212, 21_213,
@@ -482,16 +560,17 @@ module Aurora
482
560
  31_003,
483
561
  31_007, 31_010, 31_013, 31_016, 31_019, 31_022],
484
562
  "E%d" => [25, 26],
485
- "%d%%" => [282, 321, 322, 346, 565, 741, 3332, 3524],
486
- "%0.1f psi" => [419],
563
+ "%d%%" => [282, 321, 322, 325, 346, 565, 741, 1126, 3332, 3524, 3808],
564
+ "%0.1f psi" => [419, 1115, 1116, 1119, 3322, 3323],
487
565
  "%0.1fA" => [1105, 1106, 1107, 1108],
488
566
  "%0.1fgpm" => [1117],
489
- "%dW" => [1147, 1149, 1151, 1153, 1165, 3422],
490
- "%dBtuh" => [1157]
567
+ "%dW" => [1146, 1148, 1150, 1152, 1164, 3422],
568
+ "%dBtuh" => [1154, 1156]
491
569
  }.freeze
492
570
 
493
- def ignore(range)
494
- range = (range..range) if range.is_a?(Integer)
571
+ def ignore(*range)
572
+ range = range.first if range.length == 1
573
+ range = [range] if range.is_a?(Integer)
495
574
  range.zip(Array.new(range.count)).to_h
496
575
  end
497
576
 
@@ -621,7 +700,9 @@ module Aurora
621
700
  9 => "Compressor Minimum Run Time",
622
701
  15 => "Blower Off Delay",
623
702
  16 => "Line Voltage",
624
- 19 => "FP1",
703
+ 17 => "Aux/E Heat Stage", # this has some complicated condition based on
704
+ # current inputs and outputs on if it should have a value (310 - v) or (130 - v), or be 0
705
+ 19 => "FP1 (Cooling Liquid Line) Temperature",
625
706
  20 => "FP2",
626
707
  21 => "Condensate", # >= 270 normal, otherwise fault
627
708
  25 => "Last Fault Number", # high bit set if locked out
@@ -630,6 +711,7 @@ module Aurora
630
711
  28 => "System Inputs (At Last Lockout)",
631
712
  30 => "System Outputs",
632
713
  31 => "Status",
714
+ 32 => "Thermostat Input Override",
633
715
  33 => "DIP Switch Status",
634
716
  36 => "ABC Board Rev",
635
717
  45 => "Test Mode (write)", # 1 to enable
@@ -643,7 +725,8 @@ module Aurora
643
725
  88 => "ABC Program",
644
726
  92 => "Model Number",
645
727
  105 => "Serial Number",
646
- 112 => "Setup Line Voltage",
728
+ 110 => "Reheat Delay",
729
+ 112 => "Line Voltage Setting",
647
730
  201 => "Discharge Pressure", # I can't figure out how this number is represented;
648
731
  203 => "Suction Pressure",
649
732
  205 => "Discharge Temperature",
@@ -665,6 +748,7 @@ module Aurora
665
748
  321 => "VS Pump Min",
666
749
  322 => "VS Pump Max",
667
750
  323 => "VS Pump Speed Manual Control",
751
+ 325 => "VS Pump Output",
668
752
  326 => "VS Pump Fault",
669
753
  340 => "Blower Only Speed",
670
754
  341 => "Lo Compressor ECM Speed",
@@ -673,21 +757,28 @@ module Aurora
673
757
  346 => "Cooling Airflow Adjustment",
674
758
  347 => "Aux Heat ECM Speed",
675
759
  362 => "Active Dehumidify", # any value is true
676
- 460 => "IZ2??",
677
- 461 => "IZ2??",
678
- 462 => "IZ2 Status", # 5 when online; 1 when in setup mode
679
760
  400 => "DHW Enabled",
680
761
  401 => "DHW Setpoint",
681
- # 403 => "DHW Status", just a guess, based on AID Tool querying this register while showing DHW settings
762
+ 402 => "Brine Type",
763
+ 403 => "Flow Meter Type",
764
+ 404 => "Blower Type",
682
765
  405 => "SmartGrid Trigger",
683
766
  406 => "SmartGrid Action", # 0/1 for 1/2; see 414
684
767
  407 => "Off Time Length",
685
768
  408 => "HA Alarm 1 Trigger",
686
769
  409 => "HA Alarm 1 Action",
687
- 410 => "HA Alaram 2 Trigger",
770
+ 410 => "HA Alarm 2 Trigger",
688
771
  411 => "HA Alarm 2 Action",
772
+ 412 => "Energy Monitor", # 0 none, 1 compressor monitor, 2 energy monitor
773
+ 413 => "Pump Type",
689
774
  414 => "On Peak/SmartGrid", # 0x0001 only
775
+ 416 => "Energy Phase Type",
776
+ 417 => "Power Adjustment Factor L",
777
+ 418 => "Power Adjustment Factor H",
690
778
  419 => "Loop Pressure Trip",
779
+ 460 => "IZ2 Heartbeat?",
780
+ 461 => "IZ2 Heartbeat?",
781
+ 462 => "IZ2 Status", # 5 when online; 1 when in setup mode
691
782
  483 => "Number of IZ2 Zones",
692
783
  501 => "Set Point", # only read by AID tool? this is _not_ heating/cooling set point
693
784
  502 => "Ambient Temperature",
@@ -703,6 +794,9 @@ module Aurora
703
794
  800 => "Thermostat Installed",
704
795
  801 => "Thermostat Version",
705
796
  802 => "Thermostat Revision",
797
+ 803 => "??? Installed",
798
+ 804 => "??? Version",
799
+ 805 => "??? Revision",
706
800
  806 => "AXB Installed",
707
801
  807 => "AXB Version",
708
802
  808 => "AXB Revision",
@@ -731,19 +825,29 @@ module Aurora
731
825
  1106 => "Aux Amps",
732
826
  1107 => "Compressor 1 Amps",
733
827
  1108 => "Compressor 2 Amps",
734
- 1109 => "Heating Liquid Line",
828
+ 1109 => "Heating Liquid Line Temperature",
735
829
  1110 => "Leaving Water",
736
830
  1111 => "Entering Water",
737
- 1114 => "DHW Temp",
831
+ 1112 => "Leaving Air Temperature",
832
+ 1113 => "Suction Temperature",
833
+ 1114 => "DHW Temperature",
834
+ 1115 => "Discharge Pressure",
835
+ 1116 => "Suction Pressure",
738
836
  1117 => "Waterflow",
739
- 1134 => "Saturated Discharge Temperature",
740
- 1135 => "SubCooling",
741
- 1147 => "Compressor Watts",
742
- 1149 => "Blower Watts",
743
- 1151 => "Aux Watts",
744
- 1153 => "Total Watts",
745
- 1157 => "Ht of Rej",
746
- 1165 => "VS Pump Watts",
837
+ 1119 => "Loop Pressure", # only valid < 1000psi
838
+ 1124 => "Saturated Evaporator Temperature",
839
+ 1125 => "SuperHeat",
840
+ 1126 => "Vaport Injector Open %",
841
+ 1134 => "Saturated Condensor Discharge Temperature",
842
+ 1135 => "SubCooling (Heating)",
843
+ 1136 => "SubCooling (Cooling)",
844
+ 1146 => "Compressor Watts",
845
+ 1148 => "Blower Watts",
846
+ 1150 => "Aux Watts",
847
+ 1152 => "Total Watts",
848
+ 1154 => "Heat of Extraction",
849
+ 1156 => "Heat of Rejection",
850
+ 1164 => "Pump Watts",
747
851
  12_606 => "Heating Mode (write)",
748
852
  12_619 => "Heating Setpoint (write)",
749
853
  12_620 => "Cooling Setpoint (write)",
@@ -761,6 +865,11 @@ module Aurora
761
865
  3226 => "VS Drive Details (Alarm 1)",
762
866
  3227 => "VS Drive Details (Alarm 2)",
763
867
  3327 => "VS Drive Temperature",
868
+ 3322 => "VS Drive Discharge Pressure",
869
+ 3323 => "VS Drive Suction Pressure",
870
+ 3325 => "VS Drive Discharge Temperature",
871
+ 3326 => "VS Drive Compressor Ambient Temperature",
872
+ 3330 => "VS Drive Entering Water Temperature",
764
873
  3331 => "VS Drive Line Voltage",
765
874
  3332 => "VS Drive Thermo Power",
766
875
  3422 => "VS Drive Compressor Power",
@@ -769,7 +878,11 @@ module Aurora
769
878
  3523 => "VS Drive UDC Voltage",
770
879
  3524 => "VS Drive Fan Speed",
771
880
  3804 => "VS Drive Details (EEV2 Ctl)",
772
- 3904 => "Leaving Air Temperature?",
881
+ 3808 => "VS Drive SuperHeat Percent",
882
+ 3903 => "VS Drive Suction Temperature",
883
+ 3904 => "VS Drive Leaving Air Temperature?",
884
+ 3905 => "VS Drive Saturated Evaporator Discharge Temperature",
885
+ 3906 => "VS Drive SuperHeat Temperature",
773
886
  31_003 => "Outdoor Temp",
774
887
  31_005 => "IZ2 Demand",
775
888
  31_109 => "Humidifier Mode", # write to 21114
@@ -786,8 +899,8 @@ module Aurora
786
899
  .merge(faults(601..699))
787
900
  .merge(ignore(711..717))
788
901
  .merge(zone_registers)
789
- .merge(ignore(3423))
790
- .merge(ignore(3425))
902
+ .merge(ignore(1147, 1149, 1151, 1153, 1155, 1157, 1165))
903
+ .merge(ignore(3423, 3425))
791
904
  .merge(ignore(31_401..31_412))
792
905
  .merge(ignore(31_414..31_420))
793
906
  .merge(ignore(31_422..31_433))
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Aurora
4
- VERSION = "0.3.13"
4
+ VERSION = "0.4.0"
5
5
  end
data/lib/aurora.rb CHANGED
@@ -4,6 +4,7 @@ require "rmodbus"
4
4
 
5
5
  require "aurora/abc_client"
6
6
  require "aurora/iz2_zone"
7
+ require "aurora/mock_abc"
7
8
  require "aurora/thermostat"
8
9
  require "aurora/modbus/server"
9
10
  require "aurora/modbus/slave"
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.3.13
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Cody Cutrer
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2021-08-30 00:00:00.000000000 Z
11
+ date: 2021-08-31 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: ccutrer-serialport
@@ -136,6 +136,7 @@ files:
136
136
  - lib/aurora/abc_client.rb
137
137
  - lib/aurora/core_ext/string.rb
138
138
  - lib/aurora/iz2_zone.rb
139
+ - lib/aurora/mock_abc.rb
139
140
  - lib/aurora/modbus/server.rb
140
141
  - lib/aurora/modbus/slave.rb
141
142
  - lib/aurora/registers.rb