waterfurnace_aurora 0.4.4 → 0.5.2

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: c2fb8894c93d6a099f927e91a025fff0faf6a159b29808d089431640f60376b8
4
- data.tar.gz: edb0f65d15fdb706aa7471a4f3df1247e8ba354d841b9ef35c1f6874b7376eb0
3
+ metadata.gz: 5c89c3d73941d7574fbe1088bf792676f772abfb98dab07be0de27a61323ec96
4
+ data.tar.gz: 13a5a82be919ee67bbbc87357eac1cf6aaba1b34514fbd808b9411dfc37bcb45
5
5
  SHA512:
6
- metadata.gz: 2ed469c2b2cb39a166b8664e735579c7f8541eed4f9c3a2c5d7b22046321948925a8bdd2fe08bed455856cf5b36f113040bf9f8f18632f70cdbf9abca9078278
7
- data.tar.gz: a01d310d261571b7e2d1aa9537c56072820de03708eb7b9188ccae9d303f5ab54d8c6832f5949667c274247d7ef59066af3c69bd58cc80352d55db27daafbbcc
6
+ metadata.gz: ce768bdc79fc4cd2a406ebf15757f587d373030cec21efc64076b3951a7c97f631f1e47a0e810005b24553fdd78eb4aec869022c8e0f6f33e409522f473e942b
7
+ data.tar.gz: ab5cba75479435f32a42bca6fd8155a5954674996b75a4a2afe397241bce519720a083aaaf0ccf3976a8c289170ec39553f63e9ba036248bdadcf408022e7ffb
data/exe/aurora_monitor CHANGED
@@ -30,7 +30,7 @@ end
30
30
 
31
31
  options.parse!
32
32
 
33
- unless ARGV.length == 2
33
+ unless ARGV.length == 1
34
34
  puts options
35
35
  exit 1
36
36
  end
@@ -39,8 +39,8 @@ uri = URI.parse(ARGV[0])
39
39
 
40
40
  last_registers = {}
41
41
 
42
- SENSOR_REGISTERS = [16, 19, 20, 740, 900, 1109, 1105, 1106, 1107, 1108, 1110, 1111, 1114, 1117, 1134, 1147, 1149, 1151,
43
- 1153, 1165].freeze
42
+ SENSOR_REGISTERS = [16, 19, 20, 740, 900, 901, 903, 908, 909, 1109, 1105, 1106, 1107, 1108, 1110, 1111, 1114, 1117,
43
+ 1134, 1148, 1149, 1150, 1151, 1152, 1153, 1164, 1165, 3326, 3330].freeze
44
44
 
45
45
  io = case uri.scheme
46
46
  when "tcp"
@@ -76,7 +76,11 @@ diff_and_print = lambda do |registers|
76
76
  end
77
77
 
78
78
  server.request_callback = lambda { |uid, func, req|
79
- if func == 68
79
+ if func == 6
80
+ puts "#{Time.now} ===== write register to #{uid}:"
81
+ registers = { req[:addr] => req[:val] }
82
+ puts Aurora.print_registers(registers)
83
+ elsif func == 68
80
84
  puts "#{Time.now} ===== no idea to #{uid}: #{req.inspect}" unless diff_only
81
85
  elsif func == 67
82
86
  puts "#{Time.now} ===== write discontiguous registers to #{uid}:"
@@ -125,7 +129,7 @@ server.response_callback = lambda { |uid, func, res, req|
125
129
  puts "#{Time.now} ===== read discontiguous registers from #{uid}" unless diff_only
126
130
  registers = req.zip(res).to_h
127
131
  diff_and_print.call(registers)
128
- elsif [16, 67, 68].include?(func)
132
+ elsif [6, 16, 67, 68].include?(func)
129
133
  # no output
130
134
  else
131
135
  puts "#{Time.now} **** new func #{func}"
@@ -46,8 +46,20 @@ class MQTTBridge
46
46
  when /\$modbus$/
47
47
  registers = @abc.query_registers(value)
48
48
  Aurora.print_registers(registers) do |register, v|
49
- @homie.mqtt.publish("#{@homie.topic}/$modbus/#{register}", v, retain: false, qos: 1)
49
+ @homie.mqtt.publish("#{@homie.topic}/$modbus/#{register}", v, qos: 1)
50
50
  end
51
+ when %r{\$modbus/getregs}
52
+ query_id, query = value.split(":")
53
+ queries = query.split(";").map do |range|
54
+ start, length = range.split(",").map(&:to_i)
55
+ next start if length.nil?
56
+
57
+ start...(start + length)
58
+ end
59
+ registers = @abc.modbus_slave.read_multiple_holding_registers(*queries)
60
+ result = registers.values.join(",")
61
+ @homie.mqtt.publish("#{@homie.topic}/$modbus/getregs/response",
62
+ "#{query_id}:#{result}", qos: 1)
51
63
  when %r{\$modbus/(\d+)/set$}
52
64
  register = $1.to_i
53
65
  value = case value
@@ -59,7 +71,7 @@ class MQTTBridge
59
71
  @abc.modbus_slave.holding_registers[register] = value if value
60
72
  registers = { register => @abc.modbus_slave.holding_registers[register] }
61
73
  Aurora.print_registers(registers) do |r, v|
62
- @homie.mqtt.publish("#{@homie.topic}/$modbus/#{r}", v, retain: false, qos: 1)
74
+ @homie.mqtt.publish("#{@homie.topic}/$modbus/#{r}", v, qos: 1)
63
75
  end
64
76
  end
65
77
  end
@@ -76,8 +88,10 @@ class MQTTBridge
76
88
  @abc.refresh
77
89
 
78
90
  components = { @homie_abc => @abc,
91
+ @compressor => @abc.compressor,
79
92
  @blower => @abc.blower,
80
- @pump => @abc.pump }
93
+ @pump => @abc.pump,
94
+ @dhw => @abc.dhw }.compact
81
95
  @abc.zones.each_with_index do |z, idx|
82
96
  homie_zone = @homie["zone#{idx + 1}"]
83
97
  components[homie_zone] = z
@@ -89,12 +103,6 @@ class MQTTBridge
89
103
  end
90
104
  end
91
105
 
92
- if @abc.dhw?
93
- @dhw["enabled"].value = @abc.dhw_enabled
94
- @dhw["water-temperature"].value = @abc.dhw_water_temperature
95
- @dhw["set-point"].value = @abc.dhw_setpoint
96
- end
97
-
98
106
  @abc.faults.each_with_index do |fault_count, i|
99
107
  @faults["e#{i + 1}"].value = fault_count
100
108
  end
@@ -109,8 +117,6 @@ class MQTTBridge
109
117
 
110
118
  def publish_basic_attributes
111
119
  @homie_abc = @homie.node("abc", "Aurora Basic Control", "ABC") do |node|
112
- node.property("compressor-speed", "Compressor Speed", :integer, @abc.compressor_speed,
113
- format: @abc.vs_drive? ? 0..12 : 0..2)
114
120
  node.property("current-mode", "Current Heating/Cooling Mode", :enum, @abc.current_mode,
115
121
  format: %w[lockout standby blower heating cooling eh1 eh2 emergency waiting dehumidify])
116
122
  node.property("entering-air-temperature", "Entering Air Temperature", :float, @abc.entering_air_temperature,
@@ -128,13 +134,29 @@ class MQTTBridge
128
134
  format: 0..100)
129
135
  node.property("fp1", "FP1 Sensor", :float, @abc.fp1, unit: "ºF")
130
136
  node.property("fp2", "FP2 Sensor", :float, @abc.fp2, unit: "ºF")
131
- %i[compressor aux_heat total].each do |component|
137
+ %i[aux_heat total].each do |component|
132
138
  component = "#{component}_watts"
133
139
  node.property(component.tr("_", "-"), component.tr("_", " ").titleize, :integer,
134
140
  @abc.public_send(component), unit: "W")
135
141
  end
136
142
  end
137
143
 
144
+ @compressor = @homie.node("compressor", "Compressor", @abc.compressor.type) do |node|
145
+ node.property("speed", "Current compressor speed", :integer, @abc.compressor.speed,
146
+ format: @abc.compressor.speed_range)
147
+ node.property("watts", "Energy Usage", :integer, @abc.compressor.watts, unit: "W") if @abc.energy_monitoring?
148
+
149
+ next unless @abc.compressor.is_a?(Aurora::Compressor::VSDrive)
150
+
151
+ node.property("ambient-temperature", "Compressor ambient temperature", :float,
152
+ @abc.compressor.ambient_temperature, unit: "ºF")
153
+
154
+ next unless @abc.iz2?
155
+
156
+ node.property("iz2-desired-speed", "IZ2 Desired Speed", :integer, @abc.compressor.iz2_desired_speed,
157
+ format: 0..12)
158
+ end
159
+
138
160
  @blower = @homie.node("blower", "Blower", @abc.blower.type) do |node|
139
161
  if @abc.blower.respond_to?(:running)
140
162
  node.property("running", "Blower is running", :boolean, @abc.blower.running?)
@@ -154,10 +176,11 @@ class MQTTBridge
154
176
  @mutex.synchronize { property.value = @abc.blower.public_send("#{field}=", value) }
155
177
  end
156
178
  end
157
- if @abc.iz2?
158
- node.property("iz2-desired-speed", "IZ2 Desired Speed", :integer, @abc.blower.iz2_desired_speed,
159
- format: 0..100, unit: "%")
160
- end
179
+
180
+ next unless @abc.iz2?
181
+
182
+ node.property("iz2-desired-speed", "IZ2 Desired Speed", :integer, @abc.blower.iz2_desired_speed,
183
+ format: 0..100, unit: "%")
161
184
  end
162
185
 
163
186
  @pump = @homie.node("pump", "Loop Pump", @abc.pump.type) do |node|
@@ -183,21 +206,24 @@ class MQTTBridge
183
206
  end
184
207
  end
185
208
 
186
- if @abc.dhw?
209
+ if @abc.dhw
187
210
  @dhw = @homie.node("dhw", "Domestic Hot Water Generator", "DHW") do |node|
188
- node.property("enabled", "Enabled", :boolean, @abc.dhw_enabled) do |value, property|
189
- @mutex.synchronize { property.value = @abc.dhw_enabled = value }
211
+ node.property("enabled", "Enabled", :boolean, @abc.dhw.enabled) do |value, property|
212
+ @mutex.synchronize { property.value = @abc.dhw.enabled = value }
190
213
  end
191
- node.property("water-temperature", "DHW Water Temperature", :float,
192
- @abc.dhw_water_temperature, unit: "ºF")
193
- node.property("set-point", "DHW Set Point", :float, @abc.dhw_setpoint, format: 100..140,
194
- unit: "ºF") do |value, property|
195
- @mutex.synchronize { property.value = @abc.dhw_setpoint = value }
214
+ node.property("water-temperature", "Water Temperature", :float,
215
+ @abc.dhw.water_temperature, unit: "ºF")
216
+ node.property("set-point", "Set Point", :float, @abc.dhw.set_point, format: 100..140,
217
+ unit: "ºF") do |value, property|
218
+ @mutex.synchronize { property.value = @abc.dhw.set_point = value }
196
219
  end
197
220
  end
198
221
  end
199
222
 
200
223
  @faults = @homie.node("faults", "Fault History", "ABC") do |node|
224
+ node.property("clear-history", "Reset fault counts", :enum, retained: false, format: "clear") do |_|
225
+ @mutex.synchronize { @abc.clear_fault_history }
226
+ end
201
227
  @abc.faults.each_with_index do |count, i|
202
228
  name = Aurora::FAULTS[i + 1]
203
229
  node.property("e#{i + 1}", name || "E#{i + 1}", :integer, count)
@@ -213,25 +239,21 @@ class MQTTBridge
213
239
  format: allowed_modes) do |value, property|
214
240
  @mutex.synchronize { property.value = zone.target_mode = value.to_sym }
215
241
  end
216
- if zone.respond_to?(:current_mode) # TODO: implement for non-IZ2
217
- node.property("current-mode", "Current Heating/Cooling Mode Requested", :enum, zone.current_mode,
218
- format: %w[standby h1 h2 h3 c1 c2])
219
- end
242
+ node.property("current-mode", "Current Heating/Cooling Mode Requested", :enum, zone.current_mode,
243
+ format: %w[standby h1 h2 h3 c1 c2])
220
244
  node.property("target-fan-mode", "Target Fan Mode", :enum, zone.target_fan_mode,
221
245
  format: %w[auto continuous intermittent]) do |value, property|
222
246
  @mutex.synchronize { property.value = zone.target_fan_mode = value.to_sym }
223
247
  end
224
- if zone.respond_to?(:current_fan_mode) # TODO: implement for non-IZ2
225
- node.property("current-fan-mode", "Current Fan Status", :boolean, zone.current_fan_mode)
226
- node.property("fan-intermittent-on", "Fan Intermittent Mode On Duration", :enum, zone.fan_intermittent_on,
227
- unit: "M", format: %w[0 5 10 15 20]) do |value, property|
228
- @mutex.synchronize { property.value = zone.fan_intermittent_on = value.to_i }
229
- end
230
- node.property("fan-intermittent-off", "Fan Intermittent Mode Off Duration", :enum, zone.fan_intermittent_on,
231
- unit: "M", format: %w[0 5 10 15 20 25 30 35 40]) do |value, property|
232
- @mutex.synchronize { property.value = zone.fan_intermittent_on = value.to_i }
233
- end
248
+ node.property("fan-intermittent-on", "Fan Intermittent Mode On Duration", :enum, zone.fan_intermittent_on,
249
+ unit: "M", format: %w[0 5 10 15 20]) do |value, property|
250
+ @mutex.synchronize { property.value = zone.fan_intermittent_on = value.to_i }
251
+ end
252
+ node.property("fan-intermittent-off", "Fan Intermittent Mode Off Duration", :enum, zone.fan_intermittent_on,
253
+ unit: "M", format: %w[0 5 10 15 20 25 30 35 40]) do |value, property|
254
+ @mutex.synchronize { property.value = zone.fan_intermittent_on = value.to_i }
234
255
  end
256
+ node.property("current-fan-mode", "Current Fan Status", :boolean, zone.current_fan_mode)
235
257
  if zone.is_a?(Aurora::IZ2Zone)
236
258
  node.property("priority", "Zone Priority", :enum, zone.priority, format: %w[economy comfort])
237
259
  node.property("size", "Size", :enum, zone.size, format: %w[0 25 45 70])
@@ -251,6 +273,7 @@ class MQTTBridge
251
273
 
252
274
  # direct access to modbus registers for debugging purposes
253
275
  @homie.mqtt.subscribe("#{@homie.topic}/$modbus")
276
+ @homie.mqtt.subscribe("#{@homie.topic}/$modbus/getregs")
254
277
  @homie.mqtt.subscribe("#{@homie.topic}/$modbus/+/set")
255
278
  @homie.publish
256
279
  end
@@ -4,6 +4,8 @@ require "yaml"
4
4
  require "uri"
5
5
 
6
6
  require "aurora/blower"
7
+ require "aurora/compressor"
8
+ require "aurora/dhw"
7
9
  require "aurora/iz2_zone"
8
10
  require "aurora/pump"
9
11
  require "aurora/thermostat"
@@ -24,7 +26,9 @@ module Aurora
24
26
  port: uri.port || 23,
25
27
  baud: 19_200,
26
28
  parity: :even)
27
-
29
+ when "mqtt", "mqtts"
30
+ require "aurora/mqtt_modbus"
31
+ return Aurora::MQTTModBus.new(uri)
28
32
  else
29
33
  return Aurora::MockABC.new(YAML.load_file(uri.path)) if File.file?(uri.path)
30
34
 
@@ -40,23 +44,20 @@ module Aurora
40
44
  attr_reader :modbus_slave,
41
45
  :serial_number,
42
46
  :zones,
47
+ :compressor,
43
48
  :blower,
44
49
  :pump,
50
+ :dhw,
45
51
  :faults,
46
52
  :current_mode,
47
- :dhw_enabled,
48
- :dhw_setpoint,
49
53
  :entering_air_temperature,
50
54
  :relative_humidity,
51
55
  :leaving_air_temperature,
52
56
  :leaving_water_temperature,
53
57
  :entering_water_temperature,
54
- :dhw_water_temperature,
55
- :compressor_speed,
56
58
  :outdoor_temperature,
57
59
  :fp1,
58
60
  :fp2,
59
- :compressor_watts,
60
61
  :aux_heat_watts,
61
62
  :total_watts
62
63
 
@@ -68,9 +69,16 @@ module Aurora
68
69
  registers = Aurora.transform_registers(raw_registers.dup)
69
70
  @program = registers[88]
70
71
  @serial_number = registers[105]
71
- @dhw_water_temperature = registers[1114]
72
72
  @energy_monitor = raw_registers[412]
73
73
 
74
+ @zones = if iz2?
75
+ iz2_zone_count = @modbus_slave.holding_registers[483]
76
+ (0...iz2_zone_count).map { |i| IZ2Zone.new(self, i + 1) }
77
+ else
78
+ [Thermostat.new(self)]
79
+ end
80
+
81
+ @compressor = @program == "ABCVSP" ? Compressor::VSDrive.new(self) : Compressor::GenericCompressor.new(self)
74
82
  @blower = case raw_registers[404]
75
83
  when 1, 2 then Blower::ECM.new(self, registers[404])
76
84
  when 3 then Blower::FiveSpeed.new(self, registers[404])
@@ -83,13 +91,8 @@ module Aurora
83
91
  Pump::GenericPump.new(self,
84
92
  registers[413])
85
93
  end
94
+ @dhw = DHW.new(self) if (-999..999).include?(registers[1114])
86
95
 
87
- @zones = if iz2?
88
- iz2_zone_count = @modbus_slave.holding_registers[483]
89
- (0...iz2_zone_count).map { |i| IZ2Zone.new(self, i + 1) }
90
- else
91
- [Thermostat.new(self)]
92
- end
93
96
  @faults = []
94
97
  end
95
98
 
@@ -128,51 +131,32 @@ module Aurora
128
131
  end
129
132
 
130
133
  def refresh
131
- registers_to_read = [6, 19..20, 25, 30, 344, 740..741, 900, 1110..1111, 1114, 1147..1153, 1165,
134
+ registers_to_read = [6, 19..20, 25, 30, 344, 740..741, 900, 1110..1111, 1114, 1150..1153, 1165,
132
135
  31_003]
133
- registers_to_read << (400..401) if dhw?
136
+ zones.each do |z|
137
+ registers_to_read.concat(z.registers_to_read)
138
+ end
139
+ registers_to_read.concat(compressor.registers_to_read)
134
140
  registers_to_read.concat(blower.registers_to_read)
135
141
  registers_to_read.concat(pump.registers_to_read)
136
- registers_to_read.concat([362, 3001]) if vs_drive?
137
-
138
- if zones.first.is_a?(IZ2Zone)
139
- zones.each_with_index do |_z, i|
140
- base1 = 21_203 + i * 9
141
- base2 = 31_007 + i * 3
142
- base3 = 31_200 + i * 3
143
- registers_to_read << (base1..(base1 + 1))
144
- registers_to_read << (base2..(base2 + 2))
145
- registers_to_read << base3
146
- end
147
- else
148
- registers_to_read << 502
149
- registers_to_read << (745..746)
150
- end
142
+ registers_to_read.concat(dhw.registers_to_read) if dhw
143
+ # need dehumidify mode to calculate final current mode;
144
+ # apparently non-VSD doesn't have this register at all?
145
+ registers_to_read.concat([362]) if compressor.is_a?(Compressor::VSDrive)
151
146
 
152
- @faults = @modbus_slave.holding_registers[601..699]
147
+ faults = @modbus_slave.read_multiple_holding_registers(601..699)
148
+ @faults = Aurora.transform_registers(faults).values
153
149
 
154
150
  registers = @modbus_slave.holding_registers[*registers_to_read]
155
151
  Aurora.transform_registers(registers)
156
152
 
157
153
  outputs = registers[30]
158
154
 
159
- @dhw_enabled = registers[400]
160
- @dhw_setpoint = registers[401]
161
155
  @entering_air_temperature = registers[740]
162
156
  @relative_humidity = registers[741]
163
157
  @leaving_air_temperature = registers[900]
164
158
  @leaving_water_temperature = registers[1110]
165
159
  @entering_water_temperature = registers[1111]
166
- @dhw_water_temperature = registers[1114]
167
- @compressor_speed = if vs_drive?
168
- registers[3001]
169
- elsif outputs.include?(:cc2)
170
- 2
171
- elsif outputs.include?(:cc)
172
- 1
173
- else
174
- 0
175
- end
176
160
  @outdoor_temperature = registers[31_003]
177
161
  @fp1 = registers[19]
178
162
  @fp2 = registers[20]
@@ -180,7 +164,6 @@ module Aurora
180
164
  @error = registers[25] & 0x7fff
181
165
  @derated = (41..46).include?(@error)
182
166
  @safe_mode = [47, 48, 49, 72, 74].include?(@error)
183
- @compressor_watts = registers[1147]
184
167
  @aux_heat_watts = registers[1151]
185
168
  @total_watts = registers[1153]
186
169
 
@@ -202,12 +185,13 @@ module Aurora
202
185
  :standby
203
186
  end
204
187
 
205
- blower.refresh(registers)
206
- pump.refresh(registers)
207
-
208
188
  zones.each do |z|
209
189
  z.refresh(registers)
210
190
  end
191
+ compressor.refresh(registers)
192
+ blower.refresh(registers)
193
+ pump.refresh(registers)
194
+ dhw&.refresh(registers)
211
195
  end
212
196
 
213
197
  def cooling_airflow_adjustment=(value)
@@ -215,16 +199,6 @@ module Aurora
215
199
  @modbus_slave.holding_registers[346] = value
216
200
  end
217
201
 
218
- def dhw_enabled=(value)
219
- @modbus_slave.holding_registers[400] = value ? 1 : 0
220
- end
221
-
222
- def dhw_setpoint=(value)
223
- raise ArgumentError unless (100..140).include?(value)
224
-
225
- @modbus_slave.holding_registers[401] = (value * 10).to_i
226
- end
227
-
228
202
  def loop_pressure_trip=(value)
229
203
  @modbus_slave.holding_registers[419] = (value * 10).to_i
230
204
  end
@@ -270,14 +244,6 @@ module Aurora
270
244
  @energy_monitor == 2
271
245
  end
272
246
 
273
- def vs_drive?
274
- @program == "ABCVSP"
275
- end
276
-
277
- def dhw?
278
- (-999..999).include?(dhw_water_temperature)
279
- end
280
-
281
247
  # config aurora system
282
248
  { thermostat: 800, axb: 806, iz2: 812, aoc: 815, moc: 818, eev2: 824 }.each do |(component, register)|
283
249
  class_eval <<-RUBY, __FILE__, __LINE__ + 1
@@ -6,6 +6,12 @@ module Aurora
6
6
  @abc = abc
7
7
  end
8
8
 
9
+ def inspect
10
+ "#<Aurora::#{self.class.name} #{(instance_variables - [:@abc]).map do |iv|
11
+ "#{iv}=#{instance_variable_get(iv).inspect}"
12
+ end.join(', ')}>"
13
+ end
14
+
9
15
  private
10
16
 
11
17
  attr_reader :abc
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "aurora/component"
4
+
5
+ module Aurora
6
+ module Compressor
7
+ class GenericCompressor < Component
8
+ attr_reader :speed, :watts
9
+
10
+ def type
11
+ "Compressor"
12
+ end
13
+
14
+ def speed_range
15
+ 0..2
16
+ end
17
+
18
+ def registers_to_read
19
+ if abc.energy_monitoring?
20
+ [1146..1147]
21
+ else
22
+ []
23
+ end
24
+ end
25
+
26
+ def refresh(registers)
27
+ outputs = registers[30]
28
+ @speed = if outputs.include?(:cc2)
29
+ 2
30
+ elsif outputs.include?(:cc)
31
+ 1
32
+ else
33
+ 0
34
+ end
35
+ @watts = registers[1146] if abc.energy_monitoring?
36
+ end
37
+ end
38
+
39
+ class VSDrive < GenericCompressor
40
+ attr_reader :ambient_temperature, :iz2_desired_speed
41
+
42
+ def type
43
+ "Variable Speed Drive"
44
+ end
45
+
46
+ def speed_range
47
+ 0..12
48
+ end
49
+
50
+ def registers_to_read
51
+ result = super + [209, 3001, 3326]
52
+ result << 564 if abc.iz2?
53
+ result
54
+ end
55
+
56
+ def refresh(registers)
57
+ super
58
+
59
+ @speed = registers[3001]
60
+ @ambient_temperature = registers[3326]
61
+ @iz2_desired_speed = registers[564] if abc.iz2?
62
+ end
63
+ end
64
+ end
65
+ end
data/lib/aurora/dhw.rb ADDED
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "aurora/component"
4
+
5
+ module Aurora
6
+ class DHW < Component
7
+ attr_reader :enabled, :set_point, :water_temperature
8
+
9
+ def registers_to_read
10
+ [400..401, 1114]
11
+ end
12
+
13
+ def refresh(registers)
14
+ @enabled = registers[400]
15
+ @set_point = registers[401]
16
+ @water_temperature = registers[1114]
17
+ end
18
+
19
+ def enabled=(value)
20
+ holding_registers[400] = value ? 1 : 0
21
+ end
22
+
23
+ def set_point=(value) # rubocop:disable Naming/AccessorMethodName
24
+ raise ArgumentError unless (100..140).include?(value)
25
+
26
+ raw_value = (value * 10).to_i
27
+ holding_registers[401] = raw_value
28
+ @set_point = value
29
+ end
30
+ end
31
+ end
@@ -5,18 +5,22 @@ require "aurora/thermostat"
5
5
  module Aurora
6
6
  class IZ2Zone < Thermostat
7
7
  attr_reader :zone_number,
8
- :current_mode,
9
- :current_fan_mode,
10
- :fan_intermittent_on,
11
- :fan_intermittent_off,
12
8
  :priority,
13
- :size, :normalized_size
9
+ :size,
10
+ :normalized_size
14
11
 
15
12
  def initialize(abc, zone_number)
16
13
  super(abc)
17
14
  @zone_number = zone_number
18
15
  end
19
16
 
17
+ def registers_to_read
18
+ base1 = 21_203 + (zone_number - 1) * 9
19
+ base2 = 31_007 + (zone_number - 1) * 3
20
+ base3 = 31_200 + (zone_number - 1) * 3
21
+ [base1..(base1 + 1), base2..(base2 + 2), base3]
22
+ end
23
+
20
24
  def refresh(registers)
21
25
  @ambient_temperature = registers[31_007 + (zone_number - 1) * 3]
22
26
 
@@ -41,28 +45,28 @@ module Aurora
41
45
  def target_mode=(value)
42
46
  return unless (raw_value = Aurora::HEATING_MODE.invert[value])
43
47
 
44
- @abc.modbus_slave.holding_registers[21_202 + (zone_number - 1) * 9] = raw_value
48
+ holding_registers[21_202 + (zone_number - 1) * 9] = raw_value
45
49
  @target_mode = value
46
50
  end
47
51
 
48
52
  def target_fan_mode=(value)
49
53
  return unless (raw_value = Aurora::FAN_MODE.invert[value])
50
54
 
51
- @abc.modbus_slave.holding_registers[21_205 + (zone_number - 1) * 9] = raw_value
55
+ holding_registers[21_205 + (zone_number - 1) * 9] = raw_value
52
56
  @target_fan_mode = value
53
57
  end
54
58
 
55
59
  def fan_intermittent_on=(value)
56
60
  return unless value >= 0 && value <= 25 && (value % 5).zero?
57
61
 
58
- @abc.modbus_slave.holding_registers[21_206 + (zone_number - 1) * 9] = value
62
+ holding_registers[21_206 + (zone_number - 1) * 9] = value
59
63
  @fan_intermittent_on = value
60
64
  end
61
65
 
62
66
  def fan_intermittent_off=(value)
63
67
  return unless value >= 0 && value <= 40 && (value % 5).zero?
64
68
 
65
- @abc.modbus_slave.holding_registers[21_207 + (zone_number - 1) * 9] = value
69
+ holding_registers[21_207 + (zone_number - 1) * 9] = value
66
70
  @fan_intermittent_off = value
67
71
  end
68
72
 
@@ -70,7 +74,7 @@ module Aurora
70
74
  return unless value >= 40 && value <= 90
71
75
 
72
76
  raw_value = (value * 10).to_i
73
- @abc.modbus_slave.holding_registers[21_203 + (zone_number - 1) * 9] = raw_value
77
+ holding_registers[21_203 + (zone_number - 1) * 9] = raw_value
74
78
  @heating_target_temperature = value
75
79
  end
76
80
 
@@ -78,8 +82,8 @@ module Aurora
78
82
  return unless value >= 54 && value <= 99
79
83
 
80
84
  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
85
+ holding_registers[21_204 + (zone_number - 1) * 9] = raw_value
86
+ @cooling_target_temperature = value
83
87
  end
84
88
  end
85
89
  end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "mqtt"
4
+ require "securerandom"
5
+
6
+ module Aurora
7
+ class MQTTModBus
8
+ attr_accessor :logger
9
+
10
+ def initialize(uri)
11
+ @mqtt = MQTT::Client.new(uri)
12
+ @mqtt.connect
13
+
14
+ @base_topic = uri.path[1..]
15
+ @mqtt.subscribe("#{@base_topic}/getregs/response")
16
+ end
17
+
18
+ def read_multiple_holding_registers(*queries)
19
+ query_id = SecureRandom.uuid
20
+ mqtt_query = queries.map { |m| m.is_a?(Range) ? "#{m.begin},#{m.count}" : m }.join(";")
21
+ @mqtt.publish("#{@base_topic}/getregs", "#{query_id}:#{mqtt_query}", qos: 1)
22
+ Timeout.timeout(15) do
23
+ result = {}
24
+ loop do
25
+ packet = @mqtt.get
26
+ response_id, registers_flat = packet.payload.split(":")
27
+ next unless response_id == query_id
28
+
29
+ values = registers_flat.split(",").map(&:to_i)
30
+ result_index = 0
31
+ queries.each do |query|
32
+ Array(query).each do |i|
33
+ result[i] = values[result_index]
34
+ result_index += 1
35
+ end
36
+ end
37
+ break
38
+ end
39
+ result
40
+ end
41
+ end
42
+
43
+ def write_holding_register(addr, value)
44
+ @mqtt.publish("#{@base_topic}/#{addr}/set", value, qos: 1)
45
+ end
46
+ end
47
+ end
@@ -420,6 +420,15 @@ module Aurora
420
420
  end
421
421
  end
422
422
 
423
+ def thermostat_configuration2(value)
424
+ result = {
425
+ mode: HEATING_MODE[(value >> 8) & 0x07]
426
+ }
427
+ leftover = value & ~0x0700
428
+ result[:unknown] = format("0x%04x", leftover) unless leftover.zero?
429
+ result
430
+ end
431
+
423
432
  def zone_configuration1(value)
424
433
  fan = if value & 0x80 == 0x80
425
434
  :continuous
@@ -433,7 +442,7 @@ module Aurora
433
442
  on_time: ((value >> 9) & 0x7) * 5,
434
443
  off_time: (((value >> 12) & 0x7) + 1) * 5,
435
444
  cooling_target_temperature: ((value & 0x7e) >> 1) + 36,
436
- heating_target_temperature_carry: value & 0o1
445
+ heating_target_temperature_carry: value & 0x01
437
446
  }
438
447
  leftover = value & ~0x7fff
439
448
  result[:unknown] = format("0x%04x", leftover) unless leftover.zero?
@@ -476,7 +485,7 @@ module Aurora
476
485
  REGISTER_CONVERTERS = {
477
486
  TO_HUNDREDTHS => [2, 3, 417, 418, 801, 804, 807, 813, 816, 817, 819, 820, 825, 828],
478
487
  method(:dipswitch_settings) => [4, 33],
479
- TO_TENTHS => [19, 20, 401, 419, 501, 502, 567, 740, 745, 746, 747, 900,
488
+ TO_TENTHS => [19, 20, 401, 419, 501, 502, 567, 740, 745, 746, 747, 900, 901, 903,
480
489
  1105, 1106, 1107, 1108, 1109, 1110, 1111, 1112, 1113, 1114, 1115, 1116, 1117, 1119, 1124, 1125, 1134,
481
490
  3322, 3323, 3325, 3326, 3327, 3330, 3522, 3903, 3905, 3906,
482
491
  12_619, 12_620,
@@ -503,7 +512,7 @@ module Aurora
503
512
  ->(v) { from_bitmask(v, VS_ALARM2) } => [218, 3227],
504
513
  ->(v) { from_bitmask(v, VS_EEV2) } => [280, 3804],
505
514
  method(:vs_manual_control) => [323],
506
- NEGATABLE => [346, 1146],
515
+ NEGATABLE => [346],
507
516
  ->(v) { BRINE_TYPE[v] } => [402],
508
517
  ->(v) { FLOW_METER_TYPE[v] } => [403],
509
518
  ->(v) { BLOWER_TYPE[v] } => [404],
@@ -514,6 +523,7 @@ module Aurora
514
523
  ->(v) { PUMP_TYPE[v] } => [413],
515
524
  ->(v) { PHASE_TYPE[v] } => [416],
516
525
  method(:iz2_fan_desired) => [565],
526
+ ->(v) { v == 0xffff ? 0 : v } => 601..699,
517
527
  ->(registers, idx) { to_string(registers, idx, 8) } => [710],
518
528
  ->(v) { COMPONENT_STATUS[v] } => [800, 803, 806, 812, 815, 818, 824, 827],
519
529
  method(:axb_inputs) => [1103],
@@ -521,12 +531,13 @@ module Aurora
521
531
  ->(v) { TO_TENTHS.call(NEGATABLE.call(v)) } => [1135, 1136],
522
532
  method(:to_int32) => [1146, 1148, 1150, 1152, 1154, 1156, 1164, 3422, 3424],
523
533
  method(:manual_operation) => [3002],
534
+ method(:thermostat_configuration2) => [12_006],
524
535
  ->(v) { HEATING_MODE[v] } => [12_606, 21_202, 21_211, 21_220, 21_229, 21_238, 21_247],
525
536
  ->(v) { FAN_MODE[v] } => [12_621, 21_205, 21_214, 21_223, 21_232, 21_241, 21_250],
526
537
  ->(v) { from_bitmask(v, HUMIDIFIER_SETTINGS) } => [31_109],
527
538
  ->(v) { { humidification_target: v >> 8, dehumidification_target: v & 0xff } } => [31_110],
528
539
  method(:iz2_demand) => [31_005],
529
- method(:zone_configuration1) => [31_008, 31_011, 31_014, 31_017, 31_020, 31_023],
540
+ method(:zone_configuration1) => [12_005, 31_008, 31_011, 31_014, 31_017, 31_020, 31_023],
530
541
  method(:zone_configuration2) => [31_009, 31_012, 31_015, 31_018, 31_021, 31_024],
531
542
  method(:zone_configuration3) => [31_200, 31_203, 31_206, 31_209, 31_212, 31_215],
532
543
  ->(registers, idx) { to_string(registers, idx, 13) } => [31_400],
@@ -540,7 +551,7 @@ module Aurora
540
551
  REGISTER_FORMATS = {
541
552
  "%ds" => [1, 6, 9, 15, 84, 85, 110],
542
553
  "%dV" => [16, 112, 3331, 3424, 3523],
543
- "%0.1fºF" => [19, 20, 401, 501, 502, 567, 740, 745, 746, 747, 900,
554
+ "%0.1fºF" => [19, 20, 401, 501, 502, 567, 740, 745, 746, 747, 900, 903,
544
555
  1109, 1110, 1111, 1112, 1113, 1114, 1124, 1125, 1134, 1135, 1136,
545
556
  3325, 3326, 3327, 3330, 3522, 3903, 3905, 3906,
546
557
  12_619, 12_620,
@@ -553,8 +564,8 @@ module Aurora
553
564
  31_003,
554
565
  31_007, 31_010, 31_013, 31_016, 31_019, 31_022],
555
566
  "E%d" => [25, 26],
556
- "%d%%" => [282, 321, 322, 325, 346, 565, 741, 1126, 3332, 3524, 3808],
557
- "%0.1f psi" => [419, 1115, 1116, 1119, 3322, 3323],
567
+ "%d%%" => [282, 321, 322, 325, 346, 565, 741, 908, 1126, 3332, 3524, 3808],
568
+ "%0.1f psi" => [419, 901, 1115, 1116, 1119, 3322, 3323],
558
569
  "%0.1fA" => [1105, 1106, 1107, 1108],
559
570
  "%0.1fgpm" => [1117],
560
571
  "%dW" => [1146, 1148, 1150, 1152, 1164, 3422],
@@ -812,6 +823,10 @@ module Aurora
812
823
  828 => "AWL Version",
813
824
  829 => "AWL Revision",
814
825
  900 => "Leaving Air",
826
+ 901 => "Suction Pressure",
827
+ 903 => "SuperHeat Temperature",
828
+ 908 => "EEV Open %",
829
+ 909 => "SubCooling (Cooling)",
815
830
  1103 => "AXB Inputs",
816
831
  1104 => "AXB Outputs",
817
832
  1105 => "Blower Amps",
@@ -841,10 +856,6 @@ module Aurora
841
856
  1154 => "Heat of Extraction",
842
857
  1156 => "Heat of Rejection",
843
858
  1164 => "Pump Watts",
844
- 12_606 => "Heating Mode (write)",
845
- 12_619 => "Heating Setpoint (write)",
846
- 12_620 => "Cooling Setpoint (write)",
847
- 12_621 => "Fan Mode (write)",
848
859
  3000 => "Compressor Speed Desired",
849
860
  3001 => "Compressor Speed Actual",
850
861
  3002 => "Manual Operation",
@@ -876,6 +887,14 @@ module Aurora
876
887
  3904 => "VS Drive Leaving Air Temperature?",
877
888
  3905 => "VS Drive Saturated Evaporator Discharge Temperature",
878
889
  3906 => "VS Drive SuperHeat Temperature",
890
+ 12_005 => "Fan Configuration",
891
+ 12_006 => "Heating Mode",
892
+ 12_606 => "Heating Mode (write)",
893
+ 12_619 => "Heating Setpoint (write)",
894
+ 12_620 => "Cooling Setpoint (write)",
895
+ 12_621 => "Fan Mode (write)",
896
+ 12_622 => "Intermittent Fan On Time (write)",
897
+ 12_623 => "Intermittent Fan Off Time (write)",
879
898
  31_003 => "Outdoor Temp",
880
899
  31_005 => "IZ2 Demand",
881
900
  31_109 => "Humidifier Mode", # write to 21114
@@ -5,15 +5,39 @@ require "aurora/component"
5
5
  module Aurora
6
6
  class Thermostat < Component
7
7
  attr_reader :target_mode,
8
+ :current_mode,
8
9
  :target_fan_mode,
10
+ :current_fan_mode,
9
11
  :ambient_temperature,
10
12
  :cooling_target_temperature,
11
- :heating_target_temperature
13
+ :heating_target_temperature,
14
+ :fan_intermittent_on,
15
+ :fan_intermittent_off
16
+
17
+ def registers_to_read
18
+ [31, 502, 745..746, 12_005..12_006]
19
+ end
12
20
 
13
21
  def refresh(registers)
14
22
  @ambient_temperature = registers[502]
15
23
  @heating_target_temperature = registers[745]
16
24
  @cooling_target_temperature = registers[746]
25
+ config1 = registers[12_005]
26
+ config2 = registers[12_006]
27
+ @target_fan_mode = config1[:fan]
28
+ @fan_intermittent_on = config1[:on_time]
29
+ @fan_intermittent_off = config1[:off_time]
30
+ @target_mode = config2[:mode]
31
+
32
+ inputs = registers[31]
33
+ @current_fan_mode = inputs.include?(:g)
34
+ @current_mode = if inputs[:y2]
35
+ inputs[:o] ? :c2 : :h2
36
+ elsif inputs[:y1]
37
+ inputs[:o] ? :c1 : :h1
38
+ else
39
+ :standby
40
+ end
17
41
  end
18
42
 
19
43
  def target_mode=(value)
@@ -46,10 +70,18 @@ module Aurora
46
70
  @cooling_target_temperature = value
47
71
  end
48
72
 
49
- def inspect
50
- "#<Aurora::#{self.class.name} #{(instance_variables - [:@abc]).map do |iv|
51
- "#{iv}=#{instance_variable_get(iv).inspect}"
52
- end.join(', ')}>"
73
+ def fan_intermittent_on=(value)
74
+ return unless value >= 0 && value <= 25 && (value % 5).zero?
75
+
76
+ holding_registers[12_622] = value
77
+ @fan_intermittent_on = value
78
+ end
79
+
80
+ def fan_intermittent_off=(value)
81
+ return unless value >= 0 && value <= 40 && (value % 5).zero?
82
+
83
+ holding_registers[12_623] = value
84
+ @fan_intermittent_off = value
53
85
  end
54
86
  end
55
87
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Aurora
4
- VERSION = "0.4.4"
4
+ VERSION = "0.5.2"
5
5
  end
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.4.4
4
+ version: 0.5.2
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-31 00:00:00.000000000 Z
11
+ date: 2021-09-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: ccutrer-serialport
@@ -136,11 +136,14 @@ files:
136
136
  - lib/aurora/abc_client.rb
137
137
  - lib/aurora/blower.rb
138
138
  - lib/aurora/component.rb
139
+ - lib/aurora/compressor.rb
139
140
  - lib/aurora/core_ext/string.rb
141
+ - lib/aurora/dhw.rb
140
142
  - lib/aurora/iz2_zone.rb
141
143
  - lib/aurora/mock_abc.rb
142
144
  - lib/aurora/modbus/server.rb
143
145
  - lib/aurora/modbus/slave.rb
146
+ - lib/aurora/mqtt_modbus.rb
144
147
  - lib/aurora/pump.rb
145
148
  - lib/aurora/registers.rb
146
149
  - lib/aurora/thermostat.rb