waterfurnace_aurora 0.2.0 → 0.3.1

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.
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Aurora
4
+ module Inflector
5
+ def titleize
6
+ gsub(/\b(?<!\w['â`])[a-z]/, &:capitalize)
7
+ end
8
+ end
9
+ end
10
+ String.include(Aurora::Inflector)
@@ -1,29 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "aurora/thermostat"
4
+
1
5
  module Aurora
2
- class IZ2Zone
6
+ class IZ2Zone < Thermostat
3
7
  attr_reader :zone_number,
4
- :target_mode,
5
- :current_mode,
6
- :target_fan_mode,
7
- :current_fan_mode,
8
- :fan_intermittent_on,
9
- :fan_intermittent_off,
10
- :priority,
11
- :size, :normalized_size,
12
- :ambient_temperature,
13
- :cooling_target_temperature,
14
- :heating_target_temperature
8
+ :current_mode,
9
+ :current_fan_mode,
10
+ :fan_intermittent_on,
11
+ :fan_intermittent_off,
12
+ :priority,
13
+ :size, :normalized_size
15
14
 
16
15
  def initialize(abc, zone_number)
17
- @abc = abc
16
+ super(abc)
18
17
  @zone_number = zone_number
19
18
  end
20
19
 
21
20
  def refresh(registers)
22
- @ambient_temperature = registers[31007 + (zone_number - 1) * 3]
21
+ @ambient_temperature = registers[31_007 + (zone_number - 1) * 3]
23
22
 
24
- config1 = registers[31008 + (zone_number - 1) * 3]
25
- config2 = registers[31009 + (zone_number - 1) * 3]
26
- config3 = registers[31200 + (zone_number - 1) * 3]
23
+ config1 = registers[31_008 + (zone_number - 1) * 3]
24
+ config2 = registers[31_009 + (zone_number - 1) * 3]
25
+ config3 = registers[31_200 + (zone_number - 1) * 3]
27
26
 
28
27
  @target_fan_mode = config1[:fan]
29
28
  @fan_intermittent_on = config1[:on_time]
@@ -40,60 +39,47 @@ module Aurora
40
39
  end
41
40
 
42
41
  def target_mode=(value)
43
- value = Aurora::HEATING_MODE.invert[value]
44
- return unless value
45
- @abc.modbus_slave.holding_registers[21202 + (zone_number - 1) * 9] = value
46
- @target_mode = Aurora::HEATING_MODE[@abc.modbus_slave.holding_registers[21202 + (zone_number - 1) * 9]]
42
+ return unless (raw_value = Aurora::HEATING_MODE.invert[value])
43
+
44
+ @abc.modbus_slave.holding_registers[21_202 + (zone_number - 1) * 9] = raw_value
45
+ @target_mode = value
47
46
  end
48
47
 
49
48
  def target_fan_mode=(value)
50
- value = Aurora::FAN_MODE.invert[value]
51
- return unless value
52
- @abc.modbus_slave.holding_registers[21205 + (zone_number - 1) * 9] = value
53
- registers = @abc.modbus_slave.read_multiple_holding_registers(31008 + (zone_number - 1) * 3)
54
- Aurora.transform_registers(registers)
55
- @target_fan_mode = registers.first.last[:fan]
49
+ return unless (raw_value = Aurora::FAN_MODE.invert[value])
50
+
51
+ @abc.modbus_slave.holding_registers[21_205 + (zone_number - 1) * 9] = raw_value
52
+ @target_fan_mode = value
56
53
  end
57
54
 
58
55
  def fan_intermittent_on=(value)
59
- return unless value >= 0 && value <= 25 && value % 5 == 0
60
- @abc.modbus_slave.holding_registers[21206 + (zone_number - 1) * 9] = value
61
- registers = @abc.modbus_slave.read_multiple_holding_registers(31008 + (zone_number - 1) * 3)
62
- Aurora.transform_registers(registers)
63
- @fan_intermittent_on = registers.first.last[:on_time]
56
+ return unless value >= 0 && value <= 25 && (value % 5).zero?
57
+
58
+ @abc.modbus_slave.holding_registers[21_206 + (zone_number - 1) * 9] = value
59
+ @fan_intermittent_on = value
64
60
  end
65
61
 
66
62
  def fan_intermittent_off=(value)
67
- return unless value >= 0 && value <= 40 && value % 5 == 0
68
- @abc.modbus_slave.holding_registers[21207 + (zone_number - 1) * 9] = value
69
- registers = @abc.modbus_slave.read_multiple_holding_registers(31008 + (zone_number - 1) * 3)
70
- Aurora.transform_registers(registers)
71
- @fan_intermittent_on = registers.first.last[:off_time]
63
+ return unless value >= 0 && value <= 40 && (value % 5).zero?
64
+
65
+ @abc.modbus_slave.holding_registers[21_207 + (zone_number - 1) * 9] = value
66
+ @fan_intermittent_off = value
72
67
  end
73
68
 
74
69
  def heating_target_temperature=(value)
75
70
  return unless value >= 40 && value <= 90
76
- value = (value * 10).to_i
77
- @abc.modbus_slave.holding_registers[21203 + (zone_number - 1) * 9] = value
78
71
 
79
- base = 31008 + (zone_number - 1) * 3
80
- registers = @abc.modbus_slave.read_multiple_holding_registers(base..(base + 1))
81
- Aurora.transform_registers(registers)
82
- registers[base + 1][:heating_target_temperature]
72
+ raw_value = (value * 10).to_i
73
+ @abc.modbus_slave.holding_registers[21_203 + (zone_number - 1) * 9] = raw_value
74
+ @heating_target_temperature = value
83
75
  end
84
76
 
85
77
  def cooling_target_temperature=(value)
86
78
  return unless value >= 54 && value <= 99
87
- value = (value * 10).to_i
88
- @abc.modbus_slave.holding_registers[21204 + (zone_number - 1) * 9] = value
89
-
90
- registers = @abc.modbus_slave.read_multiple_holding_registers(31008 + (zone_number - 1) * 3)
91
- Aurora.transform_registers(registers)
92
- registers.first.last[:cooling_target_temperature]
93
- end
94
79
 
95
- def inspect
96
- "#<Aurora::IZ2Zone #{(instance_variables - [:@abc]).map { |iv| "#{iv}=#{instance_variable_get(iv).inspect}" }.join(', ')}>"
80
+ raw_value = (value * 10).to_i
81
+ @abc.modbus_slave.holding_registers[21_204 + (zone_number - 1) * 9] = value
82
+ @cooling_target_temperature = raw_value
97
83
  end
98
84
  end
99
85
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Aurora
2
4
  module ModBus
3
5
  module Server
@@ -5,26 +7,30 @@ module Aurora
5
7
  case func
6
8
  when 65
7
9
  # 1 function register, a multiple of two words
8
- return unless (req.length - 1) % 4 == 0
10
+ return unless ((req.length - 1) % 4).zero?
11
+
9
12
  params = []
10
- req[1..-1].unpack("n*").each_slice(2) do |(addr, quant)|
13
+ req[1..].unpack("n*").each_slice(2) do |(addr, quant)|
11
14
  params << { addr: addr, quant: quant }
12
15
  end
13
16
  params
14
17
  when 66
15
- return unless (req.length - 1) % 2 == 0
16
- req[1..-1].unpack("n*")
18
+ return unless ((req.length - 1) % 2).zero?
19
+
20
+ req[1..].unpack("n*")
17
21
  when 67
18
22
  # 1 function register, a multiple of two words
19
- return unless (req.length - 1) % 4 == 0
23
+ return unless ((req.length - 1) % 4).zero?
24
+
20
25
  params = []
21
- req[1..-1].unpack("n*").each_slice(2) do |(addr, val)|
26
+ req[1..].unpack("n*").each_slice(2) do |(addr, val)|
22
27
  params << { addr: addr, val: val }
23
28
  end
24
29
  params
25
30
  when 68
26
31
  return unless req.length == 5
27
- { noidea1: req[1,2].unpack("n"), noidea2: req[3,2].unpack("n") }
32
+
33
+ { noidea1: req[1, 2].unpack("n"), noidea2: req[3, 2].unpack("n") }
28
34
  else
29
35
  super
30
36
  end
@@ -33,7 +39,8 @@ module Aurora
33
39
  def parse_response(func, res)
34
40
  return {} if func == 67 && res.length == 1
35
41
  return { noidea: res[-1].ord } if func == 68 && res.length == 2
36
- func = 3 if func == 65 || func == 66
42
+
43
+ func = 3 if [65, 66].include?(func)
37
44
  super
38
45
  end
39
46
 
@@ -46,17 +53,17 @@ module Aurora
46
53
  return (func | 0x80).chr + err.chr
47
54
  end
48
55
 
49
- pdu += slave.holding_registers[param[:addr],param[:quant]].pack('n*')
56
+ pdu += slave.holding_registers[param[:addr], param[:quant]].pack("n*")
50
57
  end
51
- pdu = func.chr + pdu.length.chr + pdu
52
- pdu
58
+ func.chr + pdu.length.chr + pdu
59
+
53
60
  when 66
54
- pdu = params.map { |addr| slave.holding_registers[addr] }.pack('n*')
55
- pdu = func.chr + pdu.length.chr + pdu
56
- pdu
61
+ pdu = params.map { |addr| slave.holding_registers[addr] }.pack("n*")
62
+ func.chr + pdu.length.chr + pdu
63
+
57
64
  when 67
58
65
  slave.holding_registers[param[:addr]] = param[:val]
59
- pdu = req[0,2]
66
+ pdu = req[0, 2]
60
67
  else
61
68
  super
62
69
  end
@@ -1,13 +1,18 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Aurora
2
4
  module ModBus
3
5
  module Slave
4
6
  def read_multiple_holding_registers(*ranges)
5
7
  values = if ranges.any? { |r| r.is_a?(Range) }
6
- addrs_and_lengths = ranges.map { |r| r = Array(r); [r.first, r.last - r.first + 1] }.flatten
7
- query("\x41" + addrs_and_lengths.pack("n*")).unpack("n*")
8
- else
9
- query("\x42" + ranges.pack("n*")).unpack("n*")
10
- end
8
+ addrs_and_lengths = ranges.map do |r|
9
+ r = Array(r)
10
+ [r.first, r.last - r.first + 1]
11
+ end.flatten
12
+ query("A#{addrs_and_lengths.pack('n*')}").unpack("n*")
13
+ else
14
+ query("B#{ranges.pack('n*')}").unpack("n*")
15
+ end
11
16
  ranges.map { |r| Array(r) }.flatten.zip(values).to_h
12
17
  end
13
18
 
@@ -19,6 +24,7 @@ module Aurora
19
24
  class WFProxy < ::ModBus::ReadWriteProxy
20
25
  def [](*keys)
21
26
  return super if keys.length == 1
27
+
22
28
  @slave.read_multiple_holding_registers(*keys)
23
29
  end
24
30
  end
@@ -28,24 +34,25 @@ module Aurora
28
34
  # Read the slave_id and function code
29
35
  msg = read(io, 2)
30
36
  log logging_bytes(msg)
31
-
37
+
32
38
  function_code = msg.getbyte(1)
33
39
  case function_code
34
- when 1,2,3,4,65,66 then
35
- # read the third byte to find out how much more
36
- # we need to read + CRC
37
- msg += read(io, 1)
38
- msg += read(io, msg.getbyte(2)+2)
39
- when 5,6,15,16 then
40
- # We just read in an additional 6 bytes
41
- msg += read(io, 6)
42
- when 22 then
43
- msg += read(io, 8)
44
- when 0x80..0xff then
45
- msg += read(io, 3)
46
- else
47
- raise ModBus::Errors::IllegalFunction, "Illegal function: #{function_code}"
40
+ when 1, 2, 3, 4, 65, 66
41
+ # read the third byte to find out how much more
42
+ # we need to read + CRC
43
+ msg += read(io, 1)
44
+ msg += read(io, msg.getbyte(2) + 2)
45
+ when 5, 6, 15, 16
46
+ # We just read in an additional 6 bytes
47
+ msg += read(io, 6)
48
+ when 22
49
+ msg += read(io, 8)
50
+ when 0x80..0xff
51
+ msg += read(io, 3)
52
+ else
53
+ raise ModBus::Errors::IllegalFunction, "Illegal function: #{function_code}"
48
54
  end
55
+ msg
49
56
  end
50
57
  end
51
58
  end
@@ -1,5 +1,7 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Aurora
2
- extend self
4
+ module_function
3
5
 
4
6
  def normalize_ranges(ranges)
5
7
  registers = ranges.map { |r| Array(r) }.flatten.sort.uniq
@@ -9,26 +11,26 @@ module Aurora
9
11
  count = 0
10
12
  registers.each_with_index do |r, i|
11
13
  run_start ||= r
12
- if i + 1 == registers.length || r + 1 != registers[i + 1]
13
- if r == run_start
14
- result << r
15
- if (count += 1) == 100
16
- totals << result
17
- result = []
18
- count = 0
19
- end
20
- else
21
- range = run_start..r
22
- if count + range.count > 100
23
- totals << result
24
- result = []
25
- count = 0
26
- end
27
- count += range.count
28
- result << range
14
+ next unless i + 1 == registers.length || r + 1 != registers[i + 1]
15
+
16
+ if r == run_start
17
+ result << r
18
+ if (count += 1) == 100
19
+ totals << result
20
+ result = []
21
+ count = 0
22
+ end
23
+ else
24
+ range = run_start..r
25
+ if count + range.count > 100
26
+ totals << result
27
+ result = []
28
+ count = 0
29
29
  end
30
- run_start = nil
30
+ count += range.count
31
+ result << range
31
32
  end
33
+ run_start = nil
32
34
  end
33
35
  totals << result unless result.empty?
34
36
  totals
@@ -39,21 +41,20 @@ module Aurora
39
41
  TO_LAST_LOCKOUT = ->(v) { v & 0x8000 == 0x8000 ? v & 0x7fff : nil }
40
42
  NEGATABLE = ->(v) { v & 0x8000 == 0x8000 ? v - 0x10000 : v }
41
43
 
42
- def from_bitmask(v, flags)
44
+ def from_bitmask(value, flags)
43
45
  result = []
44
46
  flags.each do |(bit, flag)|
45
- result << flag if v & bit == bit
46
- v &= ~bit
47
+ result << flag if value & bit == bit
48
+ value &= ~bit
47
49
  end
48
- result << "0x%04x" % v if v != 0
50
+ result << format("0x%04x", value) unless value.zero?
49
51
  result
50
52
  end
51
53
 
52
54
  def to_string(registers, idx, length)
53
- puts "converting #{idx} of length #{length}"
54
55
  (idx...(idx + length)).map do |i|
55
56
  (registers[i] >> 8).chr + (registers[i] & 0xff).chr
56
- end.join.sub(/[ \0]+$/, '')
57
+ end.join.sub(/[ \0]+$/, "")
57
58
  end
58
59
 
59
60
  FAULTS = {
@@ -106,26 +107,27 @@ module Aurora
106
107
  75 => "Charge Loss",
107
108
  76 => "Suction Temperatur Sensor Limit",
108
109
  77 => "Leaving Air Temperature Sensor Limit",
109
- 78 => "Maximum Operating Pressure Limit",
110
- }
110
+ 78 => "Maximum Operating Pressure Limit"
111
+ }.freeze
111
112
 
112
113
  AR_SETTINGS = {
113
114
  0 => "Cycle with Compressor",
114
115
  1 => "Cycle with Thermostat Humidification Call",
115
116
  2 => "Slow Opening Water Valve",
116
117
  3 => "Cycle with Blower"
117
- }
118
+ }.freeze
119
+
120
+ def dipswitch_settings(value)
121
+ return :manual if value == 0x7fff
118
122
 
119
- def dipswitch_settings(v)
120
- return :manual if v == 0x7fff
121
123
  {
122
- fp1: v & 0x01 == 0x01 ? "30ºF" : "15ºF",
123
- fp2: v & 0x02 == 0x02 ? "30ºF" : "15ºF",
124
- rv: v & 0x04 == 0x04 ? "O" : "B",
125
- ar: AR_SETTINGS[(v >> 3) & 0x7],
126
- cc: v & 0x20 == 0x20 ? "Single Stage" : "Dual Stage",
127
- lo: v & 0x40 == 0x40 ? "Continouous" : "Pulse",
128
- dh_rh: v & 0x80 == 0x80 ? "Dehumdifier On" : "Reheat On",
124
+ fp1: value & 0x01 == 0x01 ? "30ºF" : "15ºF",
125
+ fp2: value & 0x02 == 0x02 ? "30ºF" : "15ºF",
126
+ rv: value & 0x04 == 0x04 ? "O" : "B",
127
+ ar: AR_SETTINGS[(value >> 3) & 0x7],
128
+ cc: value & 0x20 == 0x20 ? "Single Stage" : "Dual Stage",
129
+ lo: value & 0x40 == 0x40 ? "Continouous" : "Pulse",
130
+ dh_rh: value & 0x80 == 0x80 ? "Dehumdifier On" : "Reheat On"
129
131
  }
130
132
  end
131
133
 
@@ -138,8 +140,8 @@ module Aurora
138
140
  0x20 => :eh2,
139
141
  0x200 => :accessory,
140
142
  0x400 => :lockout,
141
- 0x800 => :alarm,
142
- }
143
+ 0x800 => :alarm
144
+ }.freeze
143
145
 
144
146
  SYSTEM_INPUTS = {
145
147
  0x01 => "Y1",
@@ -149,18 +151,18 @@ module Aurora
149
151
  0x10 => "G",
150
152
  0x20 => "Dehumidifer",
151
153
  0x40 => "Emergency Shutdown",
152
- 0x200 => "Load Shed",
153
- }
154
+ 0x200 => "Load Shed"
155
+ }.freeze
154
156
 
155
- def status(v)
157
+ def status(value)
156
158
  result = {
157
- lps: v & 0x80 == 0x80 ? :closed : :open,
158
- hps: v & 0x100 == 0x100 ? :closed : :open,
159
+ lps: value & 0x80 == 0x80 ? :closed : :open,
160
+ hps: value & 0x100 == 0x100 ? :closed : :open
159
161
  }
160
- result[:load_shed] = true if v & 0x0200 == 0x0200
161
- result[:emergency_shutdown] = true if v & 0x0040 == 0x0040
162
- leftover = v & ~0x03c0
163
- result[:unknown] = "0x%04x" % leftover unless leftover == 0
162
+ result[:load_shed] = true if value & 0x0200 == 0x0200
163
+ result[:emergency_shutdown] = true if value & 0x0040 == 0x0040
164
+ leftover = value & ~0x03c0
165
+ result[:unknown] = format("0x%04x", leftover) unless leftover.zero?
164
166
  result
165
167
  end
166
168
 
@@ -169,18 +171,18 @@ module Aurora
169
171
  0x04 => "Low Suction Pressure",
170
172
  0x10 => "Low Discharge Pressure",
171
173
  0x20 => "High Discharge Pressure",
172
- 0x40 => "Output Power Limit",
173
- }
174
+ 0x40 => "Output Power Limit"
175
+ }.freeze
174
176
 
175
177
  VS_SAFE_MODE = {
176
178
  0x01 => "EEV Indoor Failed",
177
179
  0x02 => "EEV Outdoor Failed",
178
- 0x04 => "Invalid Ambient Temp",
179
- }
180
+ 0x04 => "Invalid Ambient Temp"
181
+ }.freeze
180
182
 
181
183
  VS_ALARM1 = {
182
- 0x8000 => "Internal Error",
183
- }
184
+ 0x8000 => "Internal Error"
185
+ }.freeze
184
186
 
185
187
  VS_ALARM2 = {
186
188
  0x0001 => "Multi Safe Modes",
@@ -196,24 +198,24 @@ module Aurora
196
198
  0x0400 => "DC Under Voltage",
197
199
  0x0800 => "Invalid Suction Pressure",
198
200
  0x1000 => "Invalid Discharge Pressure",
199
- 0x2000 => "Low Discharge Pressure",
200
- }
201
+ 0x2000 => "Low Discharge Pressure"
202
+ }.freeze
201
203
 
202
204
  VS_EEV2 = {
203
205
  0x0010 => "Invalid Suction Temperature",
204
206
  0x0020 => "Invalid Leaving Air Temperature",
205
- 0x0040 => "Invalid Suction Pressure",
206
-
207
- }
207
+ 0x0040 => "Invalid Suction Pressure"
208
+
209
+ }.freeze
208
210
 
209
211
  AXB_INPUTS = {
210
- }
212
+ }.freeze
211
213
 
212
214
  AXB_OUTPUTS = {
213
215
  0x10 => "Accessory 2",
214
216
  0x02 => "Loop Pump",
215
217
  0x01 => "DHW"
216
- }
218
+ }.freeze
217
219
 
218
220
  HEATING_MODE = {
219
221
  0 => :off,
@@ -221,30 +223,30 @@ module Aurora
221
223
  2 => :cool,
222
224
  3 => :heat,
223
225
  4 => :eheat
224
- }
226
+ }.freeze
225
227
 
226
228
  FAN_MODE = {
227
229
  0 => :auto,
228
230
  1 => :continuous,
229
231
  2 => :intermittent
230
- }
232
+ }.freeze
231
233
 
232
234
  HUMIDIFIER_SETTINGS = {
233
235
  0x4000 => :auto_dehumidification,
234
- 0x8000 => :auto_humidification,
235
- }
236
+ 0x8000 => :auto_humidification
237
+ }.freeze
236
238
 
237
239
  INVERSE_HUMIDIFIER_SETTINGS = {
238
240
  0x4000 => :manual_dehumidification,
239
- 0x8000 => :manual_humidification,
240
- }
241
+ 0x8000 => :manual_humidification
242
+ }.freeze
241
243
 
242
244
  ZONE_SIZES = {
243
245
  0 => 0,
244
246
  1 => 25,
245
247
  2 => 45,
246
- 3 => 70,
247
- }
248
+ 3 => 70
249
+ }.freeze
248
250
 
249
251
  CALLS = {
250
252
  0x0 => :standby,
@@ -254,39 +256,39 @@ module Aurora
254
256
  0x4 => :h3,
255
257
  0x5 => :c1,
256
258
  0x6 => :c2,
257
- 0x7 => :unknown7,
258
- }
259
+ 0x7 => :unknown7
260
+ }.freeze
259
261
 
260
- def iz2_demand(v)
262
+ def iz2_demand(value)
261
263
  {
262
- fan_demand: v >> 8,
263
- unit_demand: v & 0xff,
264
+ fan_demand: value >> 8,
265
+ unit_demand: value & 0xff
264
266
  }
265
267
  end
266
268
 
267
- def zone_configuration1(v)
268
- fan = if v & 0x80 == 0x80
269
- :continuous
270
- elsif v & 0x100 == 0x100
271
- :intermittent
272
- else
273
- :auto
274
- end
269
+ def zone_configuration1(value)
270
+ fan = if value & 0x80 == 0x80
271
+ :continuous
272
+ elsif value & 0x100 == 0x100
273
+ :intermittent
274
+ else
275
+ :auto
276
+ end
275
277
  result = {
276
278
  fan: fan,
277
- on_time: ((v >> 9) & 0x7) * 5,
278
- off_time: (((v >> 12) & 0x7) + 1) * 5,
279
- cooling_target_temperature: ((v & 0x7e) >> 1) + 36,
280
- heating_target_temperature_carry: v & 01
279
+ on_time: ((value >> 9) & 0x7) * 5,
280
+ off_time: (((value >> 12) & 0x7) + 1) * 5,
281
+ cooling_target_temperature: ((value & 0x7e) >> 1) + 36,
282
+ heating_target_temperature_carry: value & 0o1
281
283
  }
282
- leftover = v & ~0x7fff
283
- result[:unknown] = "0x%04x" % leftover unless leftover == 0
284
+ leftover = value & ~0x7fff
285
+ result[:unknown] = format("0x%04x", leftover) unless leftover.zero?
284
286
  result
285
287
  end
286
288
 
287
- def zone_configuration2(registers, k)
288
- prior_v = registers[k - 1] if registers.key?(k - 1)
289
- v = registers[k]
289
+ def zone_configuration2(registers, key)
290
+ prior_v = registers[key - 1] if registers.key?(key - 1)
291
+ v = registers[key]
290
292
  result = {
291
293
  call: CALLS[(v >> 1) & 0x7],
292
294
  mode: HEATING_MODE[(v >> 8) & 0x03],
@@ -297,20 +299,20 @@ module Aurora
297
299
  result[:heating_target_temperature] = ((carry << 5) | ((v & 0xf800) >> 11)) + 36
298
300
  end
299
301
  leftover = v & ~0xfb1e
300
- result[:unknown] = "0x%04x" % leftover unless leftover == 0
302
+ result[:unknown] = format("0x%04x", leftover) unless leftover.zero?
301
303
  result
302
304
  end
303
305
 
304
306
  # hi order byte is normalized zone size
305
- def zone_configuration3(v)
306
- size = (v >> 3 ) & 0x3
307
+ def zone_configuration3(value)
308
+ size = (value >> 3) & 0x3
307
309
  result = {
308
- zone_priority: (v & 0x20) == 0x20 ? :economy : :comfort,
310
+ zone_priority: (value & 0x20) == 0x20 ? :economy : :comfort,
309
311
  zone_size: ZONE_SIZES[size],
310
- normalized_size: v >> 8,
312
+ normalized_size: value >> 8
311
313
  }
312
- leftover = v & ~0xff38
313
- result[:unknown] = "0x%04x" % leftover unless leftover == 0
314
+ leftover = value & ~0xff38
315
+ result[:unknown] = format("0x%04x", leftover) unless leftover.zero?
314
316
  result
315
317
  end
316
318
 
@@ -320,15 +322,16 @@ module Aurora
320
322
  REGISTER_CONVERTERS = {
321
323
  TO_HUNDREDTHS => [2, 3, 807, 813, 816, 817, 819, 820, 825, 828],
322
324
  method(:dipswitch_settings) => [4, 33],
323
- TO_TENTHS => [19, 20, 401, 567, 740, 745, 746, 900, 1105, 1106, 1107, 1108, 1110, 1111, 1114, 1117, 1134, 1136,
324
- 21203, 21204,
325
- 21212, 21213,
326
- 21221, 21222,
327
- 21230, 22131,
328
- 21239, 21240,
329
- 21248, 21249,
330
- 31003,
331
- 31007, 31010, 31013, 31016, 31019, 31022],
325
+ TO_TENTHS => [19, 20, 401, 567, 740, 745, 746, 747, 900, 1105, 1106, 1107, 1108, 1110, 1111, 1114, 1117, 1134, 1136,
326
+ 12_619, 12_620,
327
+ 21_203, 21_204,
328
+ 21_212, 21_213,
329
+ 21_221, 21_222,
330
+ 21_230, 22_131,
331
+ 21_239, 21_240,
332
+ 21_248, 21_249,
333
+ 31_003,
334
+ 31_007, 31_010, 31_013, 31_016, 31_019, 31_022],
332
335
  TO_LAST_LOCKOUT => [26],
333
336
  ->(v) { from_bitmask(v, SYSTEM_OUTPUTS) } => [27, 30],
334
337
  ->(v) { from_bitmask(v, SYSTEM_INPUTS) } => [28],
@@ -345,41 +348,42 @@ module Aurora
345
348
  ->(v) { from_bitmask(v, AXB_INPUTS) } => [1103],
346
349
  ->(v) { from_bitmask(v, AXB_OUTPUTS) } => [1104],
347
350
  ->(v) { TO_TENTHS.call(NEGATABLE.call(v)) } => [1136],
348
- ->(v) { HEATING_MODE[v] } => [21202, 21211, 21220, 21229, 21238, 21247],
349
- ->(v) { FAN_MODE[v] } => [21205, 21214, 21223, 21232, 21241, 21250],
350
- ->(v) { from_bitmask(v, HUMIDIFIER_SETTINGS) } => [31109],
351
- ->(v) { { humidification_target: v >> 8, dehumidification_target: v & 0xff } } => [31110],
352
- method(:iz2_demand) => [31005],
353
- method(:zone_configuration1) => [31008, 31011, 31014, 31017, 31020, 31023],
354
- method(:zone_configuration2) => [31009, 31012, 31015, 31018, 31021, 31024],
355
- method(:zone_configuration3) => [31200, 31203, 31206, 31209, 31212, 31215],
356
- ->(registers, idx) { to_string(registers, idx, 13) } => [31400],
357
- ->(registers, idx) { to_string(registers, idx, 8) } => [31413],
358
- ->(registers, idx) { to_string(registers, idx, 13) } => [31421],
359
- ->(registers, idx) { to_string(registers, idx, 13) } => [31434],
360
- ->(registers, idx) { to_string(registers, idx, 13) } => [31447],
361
- ->(registers, idx) { to_string(registers, idx, 13) } => [31460],
362
- }
351
+ ->(v) { HEATING_MODE[v] } => [12_602, 21_202, 21_211, 21_220, 21_229, 21_238, 21_247],
352
+ ->(v) { FAN_MODE[v] } => [12_621, 21_205, 21_214, 21_223, 21_232, 21_241, 21_250],
353
+ ->(v) { from_bitmask(v, HUMIDIFIER_SETTINGS) } => [31_109],
354
+ ->(v) { { humidification_target: v >> 8, dehumidification_target: v & 0xff } } => [31_110],
355
+ method(:iz2_demand) => [31_005],
356
+ method(:zone_configuration1) => [31_008, 31_011, 31_014, 31_017, 31_020, 31_023],
357
+ method(:zone_configuration2) => [31_009, 31_012, 31_015, 31_018, 31_021, 31_024],
358
+ method(:zone_configuration3) => [31_200, 31_203, 31_206, 31_209, 31_212, 31_215],
359
+ ->(registers, idx) { to_string(registers, idx, 13) } => [31_400],
360
+ ->(registers, idx) { to_string(registers, idx, 8) } => [31_413],
361
+ ->(registers, idx) { to_string(registers, idx, 13) } => [31_421],
362
+ ->(registers, idx) { to_string(registers, idx, 13) } => [31_434],
363
+ ->(registers, idx) { to_string(registers, idx, 13) } => [31_447],
364
+ ->(registers, idx) { to_string(registers, idx, 13) } => [31_460]
365
+ }.freeze
363
366
 
364
367
  REGISTER_FORMATS = {
365
368
  "%ds" => [1, 6, 9, 15, 84, 85],
366
369
  "%dV" => [16, 112],
367
- "%0.1fºF" => [19, 20, 401, 567, 740, 745, 746, 900, 1110, 1111, 1114, 1134, 1136,
368
- 21203, 21204,
369
- 21212, 21213,
370
- 21221, 21222,
371
- 21230, 21231,
372
- 21239, 21240,
373
- 21248, 21249,
374
- 31003,
375
- 31007, 31010, 31013, 31016, 31019, 31022],
370
+ "%0.1fºF" => [19, 20, 401, 567, 740, 745, 746, 747, 900, 1110, 1111, 1114, 1134, 1136,
371
+ 12_619, 12_620,
372
+ 21_203, 21_204,
373
+ 21_212, 21_213,
374
+ 21_221, 21_222,
375
+ 21_230, 21_231,
376
+ 21_239, 21_240,
377
+ 21_248, 21_249,
378
+ 31_003,
379
+ 31_007, 31_010, 31_013, 31_016, 31_019, 31_022],
376
380
  "E%d" => [25, 26],
377
381
  "%d%%" => [282, 321, 322, 346, 565, 741],
378
382
  "%0.1fA" => [1105, 1106, 1107, 1108],
379
383
  "%0.1fgpm" => [1117],
380
384
  "%dW" => [1147, 1149, 1151, 1153, 1165],
381
- "%dBtuh" => [1157],
382
- }
385
+ "%dBtuh" => [1157]
386
+ }.freeze
383
387
 
384
388
  def ignore(range)
385
389
  range.zip(Array.new(range.count)).to_h
@@ -391,11 +395,11 @@ module Aurora
391
395
 
392
396
  def zone_registers
393
397
  (1..6).map do |i|
394
- base1 = 21202 + (i - 1) * 9
395
- base2 = 31007 + (i - 1) * 3
396
- base3 = 31200 + (i - 1) * 3
398
+ base1 = 21_202 + (i - 1) * 9
399
+ base2 = 31_007 + (i - 1) * 3
400
+ base3 = 31_200 + (i - 1) * 3
397
401
  {
398
- base1 => "Zone #{i} Heating Mode",
402
+ base1 => "Zone #{i} Heating Mode (write)",
399
403
  (base1 + 1) => "Zone #{i} Heating Setpoint (write)",
400
404
  (base1 + 2) => "Zone #{i} Cooling Setpoint (write)",
401
405
  (base1 + 3) => "Zone #{i} Fan Mode (write)",
@@ -404,12 +408,12 @@ module Aurora
404
408
  base2 => "Zone #{i} Ambient Temperature",
405
409
  (base2 + 1) => "Zone #{i} Configuration 1",
406
410
  (base2 + 2) => "Zone #{i} Configuration 2",
407
- base3 => "Zone #{i} Configuration 3",
411
+ base3 => "Zone #{i} Configuration 3"
408
412
  }
409
413
  end.inject({}, &:merge)
410
414
  end
411
415
 
412
- WRITEABLE = [112, 340, 341, 342, 346, 347]
416
+ WRITEABLE = [112, 340, 341, 342, 346, 347].freeze
413
417
 
414
418
  # these are the valid ranges (i.e. the ABC will return _some_ value)
415
419
  # * means 6 sequential ranges of equal size (i.e. must be repeated for each
@@ -453,61 +457,42 @@ module Aurora
453
457
  3800..3809,
454
458
  3818..3834,
455
459
  3900..3914,
456
- 12000..12019,
457
- 12098..12099,
458
- 12100..12119,
459
- 12200..12239,
460
- 12300..12319,
461
- 12400..12569,
462
- 12600..12639,
463
- 12700..12799,
464
- 20000..20099,
465
- 21100..21136,
466
- 21200..21265,
467
- 21400..21472,
468
- 21500..21589,
469
- 22100..22162, # *
470
- 22200..22262, # *
471
- 22300..22362, # *
472
- 22400..22462, # *
473
- 22500..22562, # *
474
- 22600..22662, # *
475
- 30000..30099,
476
- 31000..31034,
477
- 31100..31129,
478
- 31200..31229,
479
- 31300..31329,
480
- 31400..31472,
481
- 32100..32162, # *
482
- 32200..32262, # *
483
- 32300..32362, # *
484
- 32400..32462, # *
485
- 32500..32562, # *
486
- 32600..32662, # *
487
- 60050..60053,
488
- 60100..60109,
489
- 60200..60200,
490
- 61000..61009
491
- ]
492
-
493
- def read_all_registers(modbus_slave)
494
- result = []
495
- REGISTER_RANGES.each do |range|
496
- # read at most 100 at a time
497
- range.each_slice(100) do |keys|
498
- result.concat(modbus_slave.holding_registers[keys.first..keys.last])
499
- end
500
- end
501
- REGISTER_RANGES.map(&:to_a).flatten.zip(result).to_h
502
- end
503
-
504
- def diff_registers(r1, r2)
505
- diff = {}
506
- r1.each_key do |k|
507
- diff[k] = [r1[k], r2[k]] if r1[k] != r2[k]
508
- end
509
- diff
510
- end
460
+ 12_000..12_019,
461
+ 12_098..12_099,
462
+ 12_100..12_119,
463
+ 12_200..12_239,
464
+ 12_300..12_319,
465
+ 12_400..12_569,
466
+ 12_600..12_639,
467
+ 12_700..12_799,
468
+ 20_000..20_099,
469
+ 21_100..21_136,
470
+ 21_200..21_265,
471
+ 21_400..21_472,
472
+ 21_500..21_589,
473
+ 22_100..22_162, # *
474
+ 22_200..22_262, # *
475
+ 22_300..22_362, # *
476
+ 22_400..22_462, # *
477
+ 22_500..22_562, # *
478
+ 22_600..22_662, # *
479
+ 30_000..30_099,
480
+ 31_000..31_034,
481
+ 31_100..31_129,
482
+ 31_200..31_229,
483
+ 31_300..31_329,
484
+ 31_400..31_472,
485
+ 32_100..32_162, # *
486
+ 32_200..32_262, # *
487
+ 32_300..32_362, # *
488
+ 32_400..32_462, # *
489
+ 32_500..32_562, # *
490
+ 32_600..32_662, # *
491
+ 60_050..60_053,
492
+ 60_100..60_109,
493
+ 60_200..60_200,
494
+ 61_000..61_009
495
+ ].freeze
511
496
 
512
497
  REGISTER_NAMES = {
513
498
  1 => "Random Start Delay",
@@ -539,7 +524,7 @@ module Aurora
539
524
  92 => "Model Number",
540
525
  105 => "Serial Number",
541
526
  112 => "Setup Line Voltage",
542
- 201 => "Discharge Pressure", # I can't figure out how this number is represented;
527
+ 201 => "Discharge Pressure", # I can't figure out how this number is represented;
543
528
  203 => "Suction Pressure",
544
529
  205 => "Discharge Temperature",
545
530
  207 => "Loop Entering Water Temperature",
@@ -565,7 +550,7 @@ module Aurora
565
550
  344 => "ECM Speed",
566
551
  346 => "Cooling Airflow Adjustment",
567
552
  347 => "Aux Heat ECM Speed",
568
- 362 => "Active Dehumidify", # any value is true
553
+ 362 => "Active Dehumidify", # any value is true
569
554
  401 => "DHW Setpoint",
570
555
  414 => "On Peak/SmartGrid 2", # 0x0001 only
571
556
  483 => "Number of IZ2 Zones",
@@ -576,6 +561,7 @@ module Aurora
576
561
  741 => "Relative Humidity",
577
562
  745 => "Heating Set Point",
578
563
  746 => "Cooling Set Point",
564
+ 747 => "Ambient Temperature",
579
565
  807 => "AXB Version",
580
566
  813 => "IZ2 Version?",
581
567
  816 => "AOC Version 1?",
@@ -604,28 +590,32 @@ module Aurora
604
590
  1153 => "Total Watts",
605
591
  1157 => "Ht of Rej",
606
592
  1165 => "VS Pump Watts",
593
+ 12_602 => "Heating Mode (write)",
594
+ 12_619 => "Heating Setpoint (write)",
595
+ 12_620 => "Cooling Setpoint (write)",
596
+ 12_621 => "Fan Mode (write)",
607
597
  3027 => "Compressor Speed",
608
- 31003 => "Outdoor Temp",
609
- 31005 => "IZ2 Demand",
610
- 31109 => "Humidifier Mode", # write to 21114
611
- 31110 => "Manual De/Humidification Target", # write to 21115
612
- 31400 => "Dealer Name",
613
- 31413 => "Dealer Phone",
614
- 31421 => "Dealer Address 1",
615
- 31434 => "Dealer Address 2",
616
- 31447 => "Dealer Email",
617
- 31460 => "Dealer Website",
618
- }.merge(ignore(89..91)).
619
- merge(ignore(93..104)).
620
- merge(ignore(106..109)).
621
- merge(faults(601..699)).
622
- merge(zone_registers).
623
- merge(ignore(31401..31412)).
624
- merge(ignore(31414..31420)).
625
- merge(ignore(31422..31433)).
626
- merge(ignore(31435..31446)).
627
- merge(ignore(31447..31459)).
628
- merge(ignore(31461..31472))
598
+ 31_003 => "Outdoor Temp",
599
+ 31_005 => "IZ2 Demand",
600
+ 31_109 => "Humidifier Mode", # write to 21114
601
+ 31_110 => "Manual De/Humidification Target", # write to 21115
602
+ 31_400 => "Dealer Name",
603
+ 31_413 => "Dealer Phone",
604
+ 31_421 => "Dealer Address 1",
605
+ 31_434 => "Dealer Address 2",
606
+ 31_447 => "Dealer Email",
607
+ 31_460 => "Dealer Website"
608
+ }.merge(ignore(89..91))
609
+ .merge(ignore(93..104))
610
+ .merge(ignore(106..109))
611
+ .merge(faults(601..699))
612
+ .merge(zone_registers)
613
+ .merge(ignore(31_401..31_412))
614
+ .merge(ignore(31_414..31_420))
615
+ .merge(ignore(31_422..31_433))
616
+ .merge(ignore(31_435..31_446))
617
+ .merge(ignore(31_447..31_459))
618
+ .merge(ignore(31_461..31_472))
629
619
 
630
620
  def transform_registers(registers)
631
621
  registers.each do |(k, v)|
@@ -638,21 +628,43 @@ module Aurora
638
628
  registers
639
629
  end
640
630
 
631
+ def read_all_registers(modbus_slave)
632
+ result = []
633
+ REGISTER_RANGES.each do |range|
634
+ # read at most 100 at a time
635
+ range.each_slice(100) do |keys|
636
+ result.concat(modbus_slave.holding_registers[keys.first..keys.last])
637
+ end
638
+ end
639
+ REGISTER_RANGES.map(&:to_a).flatten.zip(result).to_h
640
+ end
641
+
642
+ def diff_registers(lhs, rhs)
643
+ diff = {}
644
+ (lhs.keys | rhs.keys).each do |k|
645
+ diff[k] = rhs[k] if lhs[k] != rhs[k]
646
+ end
647
+ diff
648
+ end
649
+
641
650
  def print_registers(registers)
642
651
  result = []
643
- registers.each do |(k, v)|
652
+ registers.each do |(k, value)|
644
653
  # ignored
645
654
  next if REGISTER_NAMES.key?(k) && REGISTER_NAMES[k].nil?
655
+
646
656
  name = REGISTER_NAMES[k]
657
+
647
658
  value_proc = REGISTER_CONVERTERS.find { |(_, z)| z.include?(k) }&.first || ->(v) { v }
648
659
  format = REGISTER_FORMATS.find { |(_, z)| z.include?(k) }&.first || "%s"
649
660
  format = "%1$d (0x%1$04x)" unless name
650
- name ||= "???"
651
-
652
- value = value_proc.arity == 2 ? value_proc.call(registers, k) : value_proc.call(v)
661
+
662
+ value = value_proc.arity == 2 ? value_proc.call(registers, k) : value_proc.call(value)
653
663
  value = value.join(", ") if value.is_a?(Array)
654
- value = sprintf(format, value) if value
655
-
664
+ value = format(format, value) if value
665
+
666
+ name ||= "???"
667
+
656
668
  result << "#{name} (#{k}): #{value}"
657
669
  end
658
670
  result.join("\n")