Class: Aurora::ABCClient

Inherits:
Object
  • Object
show all
Defined in:
lib/aurora/abc_client.rb

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(uri) ⇒ ABCClient

Returns a new instance of ABCClient.



153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
# File 'lib/aurora/abc_client.rb', line 153

def initialize(uri)
  @modbus_slave = self.class.open_modbus_slave(uri)
  @modbus_slave.read_retry_timeout = 15
  @modbus_slave.read_retries = 2
  raw_registers = @modbus_slave.holding_registers[2, 33, 88...110, 404, 412..413, 813, 1103, 1114]
  registers = Aurora.transform_registers(raw_registers.dup)
  @abc_version = registers[2]
  @program = registers[88]
  @model = registers[92]
  @serial_number = registers[105]
  @energy_monitor = raw_registers[412]

  @zones = if iz2? && iz2_version >= 2.0
             iz2_zone_count = @modbus_slave.holding_registers[483]
             (0...iz2_zone_count).map { |i| IZ2Zone.new(self, i + 1) }
           else
             [Thermostat.new(self)]
           end

  @abc_dipswitches = registers[33]
  @axb_dipswitches = registers[1103]
  @aux_heat = AuxHeat.new(self)
  @compressor = if @program == "ABCVSP"
                  Compressor::VSDrive.new(self)
                else
                  Compressor::GenericCompressor.new(self,
                                                    @abc_dipswitches[:compressor])
                end
  @blower = case raw_registers[404]
            when 1, 2 then Blower::ECM.new(self, registers[404])
            when 3 then Blower::FiveSpeed.new(self, registers[404])
            else; Blower::PSC.new(self, registers[404])
            end
  @pump = if (3..5).cover?(raw_registers[413])
            Pump::VSPump.new(self,
                             registers[413])
          elsif axb?
            Pump::GenericPump.new(self,
                                  registers[413])
          end
  @dhw = DHW.new(self) if axb? && (-999..999).cover?(registers[1114])
  @humidistat = Humidistat.new(self,
                               @abc_dipswitches[:accessory_relay] == :humidifier,
                               axb? && @axb_dipswitches[:accessory_relay2] == :dehumidifier)
  @humidistat = nil unless @humidistat.humidifier? || @humidistat.dehumidifier? || awl_communicating?
  @faults = []

  @entering_air_register = awl_axb? ? 740 : 567
  @registers_to_read = [6, 19..20, 25, 30..31, 112, 344, @entering_air_register]
  @registers_to_read << 1104 if axb?
  @registers_to_read.push(741, 31_003) if awl_communicating?
  @registers_to_read << (1110..1111) if performance_monitoring?
  @registers_to_read.push(16, 1150..1153) if energy_monitoring?
  @registers_to_read << 900 if awl_axb?
  zones.each do |z|
    @registers_to_read.concat(z.registers_to_read)
  end
  @components = [aux_heat, compressor, blower, pump, dhw, humidistat].compact
  @components.each do |component|
    @registers_to_read.concat(component.registers_to_read)
  end
  # need dehumidify mode to calculate final current mode
  @registers_to_read.push(362) if compressor.is_a?(Compressor::VSDrive)
end

Instance Attribute Details

#abc_versionObject (readonly)

Returns the value of attribute abc_version.



116
117
118
# File 'lib/aurora/abc_client.rb', line 116

def abc_version
  @abc_version
end

#air_coil_temperatureObject (readonly)

Returns the value of attribute air_coil_temperature.



116
117
118
# File 'lib/aurora/abc_client.rb', line 116

def air_coil_temperature
  @air_coil_temperature
end

#aux_heatObject (readonly)

Returns the value of attribute aux_heat.



116
117
118
# File 'lib/aurora/abc_client.rb', line 116

def aux_heat
  @aux_heat
end

#blowerObject (readonly)

Returns the value of attribute blower.



116
117
118
# File 'lib/aurora/abc_client.rb', line 116

def blower
  @blower
end

#compressorObject (readonly)

Returns the value of attribute compressor.



116
117
118
# File 'lib/aurora/abc_client.rb', line 116

def compressor
  @compressor
end

#current_faultObject (readonly)

Returns the value of attribute current_fault.



116
117
118
# File 'lib/aurora/abc_client.rb', line 116

def current_fault
  @current_fault
end

#current_modeObject (readonly)

Returns the value of attribute current_mode.



116
117
118
# File 'lib/aurora/abc_client.rb', line 116

def current_mode
  @current_mode
end

#deratedObject (readonly) Also known as: derated?

Returns the value of attribute derated.



116
117
118
# File 'lib/aurora/abc_client.rb', line 116

def derated
  @derated
end

#dhwObject (readonly)

Returns the value of attribute dhw.



116
117
118
# File 'lib/aurora/abc_client.rb', line 116

def dhw
  @dhw
end

#emergency_shutdownObject (readonly) Also known as: emergency_shutdown?

Returns the value of attribute emergency_shutdown.



116
117
118
# File 'lib/aurora/abc_client.rb', line 116

def emergency_shutdown
  @emergency_shutdown
end

#entering_air_temperatureObject (readonly)

Returns the value of attribute entering_air_temperature.



116
117
118
# File 'lib/aurora/abc_client.rb', line 116

def entering_air_temperature
  @entering_air_temperature
end

#entering_water_temperatureObject (readonly)

Returns the value of attribute entering_water_temperature.



116
117
118
# File 'lib/aurora/abc_client.rb', line 116

def entering_water_temperature
  @entering_water_temperature
end

#faultsObject (readonly)

Returns the value of attribute faults.



116
117
118
# File 'lib/aurora/abc_client.rb', line 116

def faults
  @faults
end

#high_pressure_switchObject (readonly)

Returns the value of attribute high_pressure_switch.



116
117
118
# File 'lib/aurora/abc_client.rb', line 116

def high_pressure_switch
  @high_pressure_switch
end

#humidistatObject (readonly)

Returns the value of attribute humidistat.



116
117
118
# File 'lib/aurora/abc_client.rb', line 116

def humidistat
  @humidistat
end

#leaving_air_temperatureObject (readonly)

Returns the value of attribute leaving_air_temperature.



116
117
118
# File 'lib/aurora/abc_client.rb', line 116

def leaving_air_temperature
  @leaving_air_temperature
end

#leaving_water_temperatureObject (readonly)

Returns the value of attribute leaving_water_temperature.



116
117
118
# File 'lib/aurora/abc_client.rb', line 116

def leaving_water_temperature
  @leaving_water_temperature
end

#line_voltageObject (readonly)

Returns the value of attribute line_voltage.



116
117
118
# File 'lib/aurora/abc_client.rb', line 116

def line_voltage
  @line_voltage
end

#line_voltage_settingObject

Returns the value of attribute line_voltage_setting.



116
117
118
# File 'lib/aurora/abc_client.rb', line 116

def line_voltage_setting
  @line_voltage_setting
end

#load_shedObject (readonly) Also known as: load_shed?

Returns the value of attribute load_shed.



116
117
118
# File 'lib/aurora/abc_client.rb', line 116

def load_shed
  @load_shed
end

#locked_outObject (readonly) Also known as: locked_out?

Returns the value of attribute locked_out.



116
117
118
# File 'lib/aurora/abc_client.rb', line 116

def locked_out
  @locked_out
end

#low_pressure_switchObject (readonly)

Returns the value of attribute low_pressure_switch.



116
117
118
# File 'lib/aurora/abc_client.rb', line 116

def low_pressure_switch
  @low_pressure_switch
end

#modbus_slaveObject (readonly)

Returns the value of attribute modbus_slave.



116
117
118
# File 'lib/aurora/abc_client.rb', line 116

def modbus_slave
  @modbus_slave
end

#modelObject (readonly)

Returns the value of attribute model.



116
117
118
# File 'lib/aurora/abc_client.rb', line 116

def model
  @model
end

#outdoor_temperatureObject (readonly)

Returns the value of attribute outdoor_temperature.



116
117
118
# File 'lib/aurora/abc_client.rb', line 116

def outdoor_temperature
  @outdoor_temperature
end

#pumpObject (readonly)

Returns the value of attribute pump.



116
117
118
# File 'lib/aurora/abc_client.rb', line 116

def pump
  @pump
end

#safe_modeObject (readonly) Also known as: safe_mode?

Returns the value of attribute safe_mode.



116
117
118
# File 'lib/aurora/abc_client.rb', line 116

def safe_mode
  @safe_mode
end

#serial_numberObject (readonly)

Returns the value of attribute serial_number.



116
117
118
# File 'lib/aurora/abc_client.rb', line 116

def serial_number
  @serial_number
end

#wattsObject (readonly)

Returns the value of attribute watts.



116
117
118
# File 'lib/aurora/abc_client.rb', line 116

def watts
  @watts
end

#zonesObject (readonly)

Returns the value of attribute zones.



116
117
118
# File 'lib/aurora/abc_client.rb', line 116

def zones
  @zones
end

Class Method Details

.open_modbus_slave(uri, ignore_missing_registers: false) ⇒ Object



18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
# File 'lib/aurora/abc_client.rb', line 18

def open_modbus_slave(uri, ignore_missing_registers: false)
  uri = URI.parse(uri)

  io = case uri.scheme
       when "tcp"
         require "socket"
         TCPSocket.new(uri.host, uri.port)
       when "telnet", "rfc2217"
         require "net/telnet/rfc2217"
         Net::Telnet::RFC2217.new(uri.host,
                                  port: uri.port || 23,
                                  baud: 19_200,
                                  parity: :even)
       when "mqtt", "mqtts"
         require "aurora/mqtt_modbus"
         return Aurora::MQTTModBus.new(uri)
       else
         if File.file?(uri.path)
           return Aurora::MockABC.new(YAML.load_file(uri.path),
                                      ignore_missing_registers: ignore_missing_registers)
         end

         require "ccutrer-serialport"
         CCutrer::SerialPort.new(uri.path, baud: 19_200, parity: :even)
       end

  client = ::ModBus::RTUClient.new(io)
  client.with_slave(1)
end

.query_registers(modbus_slave, query, try_individual: false) ⇒ Object



48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
# File 'lib/aurora/abc_client.rb', line 48

def query_registers(modbus_slave, query, try_individual: false)
  implicit = try_individual
  ranges = query.split(",").map do |addr|
    case addr
    when "known"
      implicit = true
      try_individual = true if try_individual.nil?
      Aurora::REGISTER_NAMES.keys
    when "valid"
      implicit = true
      try_individual = true if try_individual.nil?
      break Aurora::REGISTER_RANGES
    when "all"
      implicit = true
      try_individual = true if try_individual.nil?
      break 0..65_535
    when /^(\d+)(?:\.\.|-)(\d+)$/
      $1.to_i..$2.to_i
    else
      addr.to_i
    end
  end
  queries = Aurora.normalize_ranges(ranges)
  registers = {}
  last_log_time = nil
  queries.each do |subquery|
    last_log_time = log_query(last_log_time, subquery.inspect)
    registers.merge!(modbus_slave.read_multiple_holding_registers(*subquery))
  rescue ::ModBus::Errors::IllegalDataAddress, ::ModBus::Errors::IllegalFunction, ::ModBus::Errors::ModBusTimeout
    # maybe this unit doesn't respond to all the addresses we want?
    raise unless implicit

    # try each query individually
    subquery.each do |subsubquery|
      last_log_time = log_query(last_log_time, subsubquery.inspect)
      registers.merge!(modbus_slave.read_multiple_holding_registers(subsubquery))
    rescue ::ModBus::Errors::IllegalDataAddress,
           ::ModBus::Errors::IllegalFunction,
           ::ModBus::Errors::ModBusTimeout => e
      raise if e.is_a?(::ModBus::Errors::ModBusTimeout) && !try_individual
      next unless try_individual

      # seriously?? try each register individually
      Array(subsubquery).each do |i|
        last_log_time = log_query(last_log_time, i.to_s)
        registers[i] = modbus_slave.holding_registers[i]
      rescue ::ModBus::Errors::IllegalDataAddress, ::ModBus::Errors::IllegalFunction
        # don't catch ModBusTimeout here... it should have no problem responding to a single register request
        next
      end
    end
  end
  registers
end

Instance Method Details

#awl_axb?Boolean

is the AXB AWL compliant?

Returns:

  • (Boolean)


385
386
387
# File 'lib/aurora/abc_client.rb', line 385

def awl_axb?
  axb? && axb_version >= 2.0
end

#awl_communicating?Boolean

see www.waterfurnace.com/literature/symphony/ig2001ew.pdf is there a communicating system compliant with AWL?

Returns:

  • (Boolean)


370
371
372
# File 'lib/aurora/abc_client.rb', line 370

def awl_communicating?
  awl_thermostat? || awl_iz2?
end

#awl_iz2?Boolean

is the IZ2 AWL compliant?

Returns:

  • (Boolean)


380
381
382
# File 'lib/aurora/abc_client.rb', line 380

def awl_iz2?
  iz2? && iz2_version >= 2.0
end

#awl_thermostat?Boolean

is the thermostat AWL compliant?

Returns:

  • (Boolean)


375
376
377
# File 'lib/aurora/abc_client.rb', line 375

def awl_thermostat?
  thermostat? && thermostat_version >= 3.0
end

#clear_fault_historyObject



296
297
298
# File 'lib/aurora/abc_client.rb', line 296

def clear_fault_history
  @modbus_slave.holding_registers[47] = 0x5555
end

#cooling_airflow_adjustment=(value) ⇒ Object



281
282
283
284
# File 'lib/aurora/abc_client.rb', line 281

def cooling_airflow_adjustment=(value)
  value = 0x10000 + value if value.negative?
  @modbus_slave.holding_registers[346] = value
end

#energy_monitoring?Boolean

Returns:

  • (Boolean)


341
342
343
# File 'lib/aurora/abc_client.rb', line 341

def energy_monitoring?
  @energy_monitor == 2
end

#inspectObject



389
390
391
392
393
# File 'lib/aurora/abc_client.rb', line 389

def inspect
  "#<Aurora::ABCClient #{(instance_variables - [:@modbus_slave]).map do |iv|
                           "#{iv}=#{instance_variable_get(iv).inspect}"
                         end.join(", ")}>"
end

#loop_pressure_trip=(value) ⇒ Object



286
287
288
# File 'lib/aurora/abc_client.rb', line 286

def loop_pressure_trip=(value)
  @modbus_slave.holding_registers[419] = (value * 10).to_i
end

#manual_operation(mode: :off, compressor_speed: 0, blower_speed: :with_compressor, pump_speed: :with_compressor, aux_heat: false) ⇒ Object

Raises:

  • (ArgumentError)


300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
# File 'lib/aurora/abc_client.rb', line 300

def manual_operation(mode: :off,
                     compressor_speed: 0,
                     blower_speed: :with_compressor,
                     pump_speed: :with_compressor,
                     aux_heat: false)
  raise ArgumentError, "mode must be :off, :heating, or :cooling" unless %i[off heating cooling].include?(mode)
  raise ArgumentError, "compressor speed must be between 0 and 12" unless (0..12).cover?(compressor_speed)

  unless blower_speed == :with_compressor || (0..12).cover?(blower_speed)
    raise ArgumentError,
          "blower speed must be :with_compressor or between 0 and 12"
  end
  unless pump_speed == :with_compressor || (0..100).cover?(pump_speed)
    raise ArgumentError,
          "pump speed must be :with_compressor or between 0 and 100"
  end

  value = 0
  value = 0x7fff if mode == :off
  value |= 0x100 if mode == :cooling
  value |= (blower_speed == :with_compressor) ? 0xf0 : (blower_speed << 4)
  value |= 0x200 if aux_heat

  @modbus_slave.holding_registers[3002] = value
  @modbus_slave.holding_registers[323] = (pump_speed == :with_compressor) ? 0x7fff : pump_speed
end

#performance_monitoring?Boolean

I’m not sure if this is correct. 5 Series documentation says that performance monitoring is an optional kit that requires an AXB, but AID Tool Sensor Kit Setup only lets you choose between None, Compressor Monitor, and Energy Monitor. So I’m assuming for now that any AXB has at least performance monitoring.

Returns:

  • (Boolean)


333
334
335
# File 'lib/aurora/abc_client.rb', line 333

def performance_monitoring?
  axb?
end

#query_registers(query) ⇒ Object



218
219
220
# File 'lib/aurora/abc_client.rb', line 218

def query_registers(query)
  self.class.query_registers(@modbus_slave, query)
end

#refreshObject



222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
# File 'lib/aurora/abc_client.rb', line 222

def refresh
  faults = @modbus_slave.read_multiple_holding_registers(601..699)
  @faults = Aurora.transform_registers(faults).values

  registers = @modbus_slave.holding_registers[*@registers_to_read]
  Aurora.transform_registers(registers)

  outputs = registers[30]

  @entering_air_temperature   = registers[@entering_air_register]
  @leaving_air_temperature    = registers[900] if awl_axb?
  if performance_monitoring?
    @leaving_water_temperature  = registers[1110]
    @entering_water_temperature = registers[1111]
  end
  @outdoor_temperature        = registers[31_003]
  @air_coil_temperature       = registers[20]
  @locked_out                 = registers[25].anybits?(0x8000)
  @current_fault              = registers[25] & 0x7fff
  @derated                    = (41..46).cover?(@current_fault)
  @safe_mode                  = [47, 48, 49, 72, 74].include?(@current_fault)
  @low_pressure_switch        = registers[31][:lps]
  @high_pressure_switch       = registers[31][:hps]
  @emergency_shutdown         = !!registers[31][:emergency_shutdown]
  @load_shed                  = !!registers[31][:load_shed]
  @line_voltage               = registers[16] if energy_monitoring?
  @line_voltage_setting       = registers[112]
  @watts                      = registers[1153]

  @current_mode = if outputs.include?(:lockout)
                    :lockout
                  elsif registers[362]
                    :dehumidify
                  elsif outputs.include?(:cc2) || outputs.include?(:cc)
                    if outputs.include?(:rv)
                      :cooling
                    elsif outputs.include?(:eh2) || outputs.include?(:eh1)
                      :heating_with_aux
                    else
                      :heating
                    end
                  elsif outputs.include?(:eh2) || outputs.include?(:eh1)
                    :emergency_heat
                  elsif outputs.include?(:blower)
                    :blower
                  elsif !registers[6].zero?
                    :waiting
                  else
                    :standby
                  end

  zones.each do |z|
    z.refresh(registers)
  end
  @components.each do |component|
    component.refresh(registers)
  end
end

#refrigeration_monitoring?Boolean

Returns:

  • (Boolean)


337
338
339
# File 'lib/aurora/abc_client.rb', line 337

def refrigeration_monitoring?
  @energy_monitor >= 1
end