waterfurnace_aurora 0.3.13 → 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: 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