Module: Aurora

Defined in:
lib/aurora/dhw.rb,
lib/aurora/pump.rb,
lib/aurora/blower.rb,
lib/aurora/version.rb,
lib/aurora/aux_heat.rb,
lib/aurora/iz2_zone.rb,
lib/aurora/mock_abc.rb,
lib/aurora/component.rb,
lib/aurora/registers.rb,
lib/aurora/abc_client.rb,
lib/aurora/compressor.rb,
lib/aurora/humidistat.rb,
lib/aurora/thermostat.rb,
lib/aurora/mqtt_modbus.rb,
lib/aurora/modbus/slave.rb,
lib/aurora/web_aid_tool.rb,
lib/aurora/modbus/server.rb,
lib/aurora/core_ext/string.rb,
lib/aurora.rb

Defined Under Namespace

Modules: Blower, Compressor, Inflector, ModBus, Pump Classes: ABCClient, AuxHeat, Component, DHW, Humidistat, IZ2Zone, MQTTModBus, MockABC, Thermostat, WebAIDTool

Constant Summary collapse

VERSION =
"1.5.3"
NEGATABLE =
->(v) { (v & 0x8000 == 0x8000) ? v - 0x10000 : v }
TO_HUNDREDTHS =
->(v) { v.to_f / 100 }
TO_TENTHS =
->(v) { v.to_f / 10 }
TO_SIGNED_TENTHS =
->(v) { NEGATABLE.call(v).to_f / 10 }
TO_LAST_LOCKOUT =
->(v) { (v & 0x8000 == 0x8000) ? v & 0x7fff : nil }
FAULTS =
{
  # ABC/AXB Basic Faults
  1 => "Input Error", # Tstat input error. Autoreset upon condition removal.
  2 => "High Pressure", # HP switch has tripped (>600 psi)
  3 => "Low Pressure", # Low Pressure Switch has tripped (<40 psi for 30 continous sec.)
  4 => "Freeze Detect FP2", # Freeze protection sensor has tripped (<15 or 30 degF for 30 continuous sec.)
  5 => "Freeze Detect FP1", # Freeze protection sensor has tripped (<15 or 30 degF for 30 continuous sec.)
  7 => "Condensate Overflow", # Condensate switch has shown continuity for 30 continuous sec.
  8 => "Over/Under Voltage", # Instantaneous Voltage is out of range. **Controls shut down until resolved.
  9 => "AirF/RPM",
  10 => "Compressor Monitor", # Open Crkt, Run, Start or welded cont
  11 => "FP1/2 Sensor Error",
  12 => "RefPerfrm Error",
  # Miscellaneous
  13 => "Non-Critical AXB Sensor Error", # Any Other Sensor Error
  14 => "Critical AXB Sensor Error", # Sensor Err for EEV or HW
  15 => "Hot Water Limit", # HW over limit or logic lockout. HW pump deactivated.
  16 => "VS Pump Error", # Alert is read from PWM feedback.
  17 => "Communicating Thermostat Error",
  18 => "Non-Critical Communications Error", # Any non-critical com error
  19 => "Critical Communications Error", # Any critical com error. Auto reset upon condition removal
  21 => "Low Loop Pressure", # Loop pressure is below 3 psi for more than 3 minutes
  22 => "Communicating ECM Error",
  23 => "HA Alarm 1", # Closed contact input is present on Dig 2 input - Text is configurable.
  24 => "HA Alarm 2", # Closed contact input is present on Dig 3 input - Text is configurable.
  25 => "AxbEev Error",
  # VS Drive
  41 => "High Drive Temp", # Drive Temp has reached critical High Temp (>239 ̊F/115 ̊C)
  42 => "High Discharge Temp", # Discharge temperature has reached critical high temp (> 280 ̊F/138 ̊C)
  43 => "Low Suction Pressure", # Suction Pressure is critically low (< 28 psig)
  44 => "Low Condensing Pressure", # Condensing pressure is critically low (< 119 psig)
  45 => "High Condensing Pressure", # Condensing pressure is critically high (> 654 psig)
  46 => "Output Power Limit", # Supply Voltage is <208V or Max Pwr is reached due to high pressure
  47 => "EEV ID Comm Error", # Com with EEV is interupted EEV has gone independent mode
  48 => "EEV OD Comm Error", # Com with EEV is interupted EEV has gone independent mode
  49 => "Cabinet Temperature Sensor", # Ambient Temperature (Tamb) is <-76 or > 212 F and out of range or invalid
  51 => "Discharge Temp Sensor", # Discharge Sensor (Sd) is > 280 F or invalid (-76 to 392 F)
  52 => "Suction Pressure Sensor", # Suction Pressure (P0) is invalid (0 to 232 psi)
  53 => "Condensing Pressure Sensor", # Low condensing pressure (PD) or invalid (0 to 870 psi) Retry 10x.
  54 => "Low Supply Voltage", # Supply Voltage is <180 V (190V to reset) or powered off/on too quickly (<30 sec.).
  55 => "Out of Envelope", # Comp Operating out of envelope (P0) more than 90 sec. Retry 10x.
  56 => "Drive Over Current", # Over current tripped by phase loss, earth fault, short circuit, low water flow, low air flow, or major drive fault. # rubocop:disable Layout/LineLength
  57 => "Drive Over/Under Voltage", # DC Link Voltage to compressor is >450vdc or at minimum voltage (<185vdc).
  58 => "High Drive Temp", # Drive Temp has reached critical High Temp >239 F
  59 => "Internal Drive Error", # The MOC has encountered an internal fault or an internal error. Probably fatal.
  61 => "Multiple Safe Mode", # More than one SafeMode condition is present requiring lockout
  # EEV2
  71 => "Loss of Charge", # High superheat and high EEV opening % for a long time will trigger a loss of charge fault
  72 => "Suction Temperature Sensor", # Suction Temperature Sensor is invalid (-76 to 392 F)
  73 => "Leaving Air Temperature Sensor", # Leaving Air Temperature Sensor is invalid (-76 to 392 F)
  74 => "Maximum Operating Pressure", # Suction pressure has exceeded that maximum operating level for 90 sec.
  99 => "System Reset"
}.freeze
SMARTGRID_ACTION =
{
  0 => :none,
  1 => :unoccupied_set_points,
  2 => :load_shed,
  3 => :capacity_limiting,
  4 => :off_time
}.freeze
HA_ALARM =
{
  0 => :none,
  1 => :general,
  2 => :security,
  3 => :sump,
  4 => :carbon_monoxide,
  5 => :dirty_filter
}.freeze
BRINE_TYPE =
Hash.new("Water").merge!(
  485 => "Antifreeze"
).freeze
FLOW_METER_TYPE =
Hash.new("Other").merge!(
  0 => "None",
  1 => '3/4"',
  2 => '1"'
).freeze
PUMP_TYPE =
Hash.new("Other").merge!(
  0 => "Open Loop",
  1 => "FC1",
  2 => "FC2",
  3 => "VS Pump",
  4 => "VS Pump + 26-99",
  5 => "VS Pump + UPS26-99",
  6 => "FC1_GLNP",
  7 => "FC2_GLNP"
).freeze
PHASE_TYPE =
Hash.new("Other").merge!(
  0 => "Single",
  1 => "Three"
).freeze
BLOWER_TYPE =
Hash.new("Other").merge!(
  0 => "PSC",
  1 => "ECM 208/230",
  2 => "ECM 265/277",
  3 => "5 Speed ECM 460"
).freeze
ENERGY_MONITOR_TYPE =
Hash.new("Other").merge!(
  0 => "None",
  1 => "Compressor Monitor",
  2 => "Energy Monitor"
).freeze
VS_FAULTS =
{
  "Under-Voltage Warning" => (71..77),
  "RPM Sensor Signal Fault" => (78..82),
  "Under-Voltage Stop" => (83..87),
  "Rotor Locked" => (88..92),
  "Standby" => (93..99) # should be infinite, but ruby 2.5 doesn't support it
}.freeze
ACCESSORY_RELAY_SETTINGS =
{
  0 => :compressor,
  1 => :slow_opening_water_valve,
  2 => :humidifier,
  3 => :blower
}.freeze
COMPONENT_STATUS =
{
  1 => :active,
  2 => :added,
  3 => :removed,
  0xffff => :missing
}.freeze
SYSTEM_OUTPUTS =
{
  0x01 => :cc, # compressor stage 1
  0x02 => :cc2, # compressor stage 2
  0x04 => :rv, # reversing valve (cool instead of heat)
  0x08 => :blower,
  0x10 => :eh1,
  0x20 => :eh2,
  # 0x40 => ??, # this turns on and off quite a bit during normal operation
  # 0x80 => ??, # this turns on occasionally during normal operation; I've only seen it when aux heat is on
  0x200 => :accessory,
  0x400 => :lockout,
  0x800 => :alarm
}.freeze
SYSTEM_INPUTS =
{
  0x01 => :y1,
  0x02 => :y2,
  0x04 => :w,
  0x08 => :o,
  0x10 => :g,
  0x20 => :dh_rh,
  0x40 => :emergency_shutdown,
  0x200 => :load_shed
}.freeze
VS_DRIVE_DERATE =
{
  0x01 => "Drive Over Temp",
  0x04 => "Low Suction Pressure",
  0x10 => "Low Discharge Pressure",
  0x20 => "High Discharge Pressure",
  0x40 => "Output Power Limit"
}.freeze
VS_SAFE_MODE =
{
  0x01 => "EEV Indoor Failed",
  0x02 => "EEV Outdoor Failed",
  0x04 => "Invalid Ambient Temp"
}.freeze
VS_ALARM1 =
{
  0x8000 => "Internal Error"
}.freeze
VS_ALARM2 =
{
  0x0001 => "Multi Safe Modes",
  0x0002 => "Out of Envelope",
  0x0004 => "Over Current",
  0x0008 => "Over Voltage",
  0x0010 => "Drive Over Temp",
  0x0020 => "Under Voltage",
  0x0040 => "High Discharge Temp",
  0x0080 => "Invalid Discharge Temp",
  0x0100 => "OEM Communications Timeout",
  0x0200 => "MOC Safety",
  0x0400 => "DC Under Voltage",
  0x0800 => "Invalid Suction Pressure",
  0x1000 => "Invalid Discharge Pressure",
  0x2000 => "Low Discharge Pressure"
}.freeze
VS_EEV2 =
{
  0x0010 => "Invalid Suction Temperature",
  0x0020 => "Invalid Leaving Air Temperature",
  0x0040 => "Invalid Suction Pressure"

}.freeze
AXB_OUTPUTS =
{
  0x01 => :dhw,
  0x02 => :loop_pump,
  0x04 => :diverting_valve,
  0x08 => :dehumidifer_reheat,
  0x10 => :accessory2
}.freeze
HEATING_MODE =
{
  0 => :off,
  1 => :auto,
  2 => :cool,
  3 => :heat,
  4 => :eheat
}.freeze
FAN_MODE =
{
  0 => :auto,
  1 => :continuous,
  2 => :intermittent
}.freeze
HUMIDIFIER_SETTINGS =
{
  0x4000 => :auto_dehumidification,
  0x8000 => :auto_humidification
}.freeze
INVERSE_HUMIDIFIER_SETTINGS =
{
  0x4000 => :manual_dehumidification,
  0x8000 => :manual_humidification
}.freeze
ZONE_SIZES =
{
  0 => 0,
  1 => 25,
  2 => 45,
  3 => 70
}.freeze
CALLS =
{
  0x0 => :standby,
  0x1 => :unknown1,
  0x2 => :h1,
  0x3 => :h2,
  0x4 => :h3,
  0x5 => :c1,
  0x6 => :c2,
  0x7 => :unknown7
}.freeze
REGISTER_CONVERTERS =

intermittent on time allowed: 0, 5, 10, 15, 20 intermittent off time allowed: 5, 10, 15, 20, 25, 30, 35, 40

{
  TO_HUNDREDTHS => [2, 3, 417, 418, 801, 804, 807, 813, 816, 817, 819, 820, 825, 828],
  method(:dipswitch_settings) => [4, 33],
  # rubocop:disable Layout/MultilineArrayLineBreaks
  TO_TENTHS => [401, 419, 745, 746, 901,
                1105, 1106, 1107, 1108, 1115, 1116, 1117, 1119,
                3322, 3323,
                12_619, 12_620,
                21_203, 21_204,
                21_212, 21_213,
                21_221, 21_222,
                21_230, 22_131,
                21_239, 21_240,
                21_248, 21_249],
  TO_SIGNED_TENTHS => [19, 20, 501, 502, 567, 740, 747, 900, 903,
                       1109, 1110, 1111, 1112, 1113, 1114, 1124, 1125, 1134, 1135, 1136,
                       3325, 3326, 3327, 3330, 3522, 3903, 3905, 3906,
                       31_003, 31_007, 31_010, 31_013, 31_016, 31_019, 31_022],
  # rubocop:enable Layout/MultilineArrayLineBreaks
  TO_LAST_LOCKOUT => [26],
  ->(v) { from_bitmask(v, SYSTEM_OUTPUTS) } => [27, 30],
  ->(v) { from_bitmask(v, SYSTEM_INPUTS) } => [28],
  method(:status) => [31],
  method(:thermostat_override) => [32],
  ->(v) { !v.zero? } => [45, 362, 400],
  ->(registers, idx) { to_string(registers, idx, 4) } => [88],
  ->(registers, idx) { to_string(registers, idx, 12) } => [92],
  ->(registers, idx) { to_string(registers, idx, 5) } => [105],
  ->(v) { from_bitmask(v, VS_DRIVE_DERATE) } => [214, 3223],
  ->(v) { from_bitmask(v, VS_SAFE_MODE) } => [216, 3225],
  ->(v) { from_bitmask(v, VS_ALARM1) } => [217, 3226],
  ->(v) { from_bitmask(v, VS_ALARM2) } => [218, 3227],
  ->(v) { from_bitmask(v, VS_EEV2) } => [280, 3804],
  method(:vs_manual_control) => [323],
  NEGATABLE => [346],
  ->(v) { BRINE_TYPE[v] } => [402],
  ->(v) { FLOW_METER_TYPE[v] } => [403],
  ->(v) { BLOWER_TYPE[v] } => [404],
  ->(v) { v.zero? ? :closed : :open } => [405, 408, 410],
  ->(v) { SMARTGRID_ACTION[v] } => [406],
  ->(v) { HA_ALARM[v] } => [409, 411],
  ->(v) { ENERGY_MONITOR_TYPE[v] } => [412],
  ->(v) { PUMP_TYPE[v] } => [413],
  ->(v) { PHASE_TYPE[v] } => [416],
  method(:iz2_fan_desired) => [565],
  ->(registers, idx) { to_string(registers, idx, 8) } => [710],
  ->(v) { COMPONENT_STATUS[v] } => [800, 803, 806, 812, 815, 818, 824, 827],
  method(:axb_inputs) => [1103],
  ->(v) { from_bitmask(v, AXB_OUTPUTS) } => [1104],
  method(:to_uint32) => [1146, 1148, 1150, 1152, 1164, 3422, 3424],
  method(:to_int32) => [1154, 1156],
  method(:manual_operation) => [3002],
  method(:thermostat_configuration2) => [12_006],
  ->(v) { HEATING_MODE[v] } => [12_606, 21_202, 21_211, 21_220, 21_229, 21_238, 21_247],
  ->(v) { FAN_MODE[v] } => [12_621, 21_205, 21_214, 21_223, 21_232, 21_241, 21_250],
  ->(v) { from_bitmask(v, HUMIDIFIER_SETTINGS) } => [12_309, 21_114, 31_109],
  ->(v) { { humidification_target: v >> 8, dehumidification_target: v & 0xff } } => [12_310, 21_115, 31_110],
  method(:iz2_demand) => [31_005],
  method(:zone_configuration1) => [12_005, 31_008, 31_011, 31_014, 31_017, 31_020, 31_023],
  method(:zone_configuration2) => [31_009, 31_012, 31_015, 31_018, 31_021, 31_024],
  method(:zone_configuration3) => [31_200, 31_203, 31_206, 31_209, 31_212, 31_215],
  ->(registers, idx) { to_string(registers, idx, 13) } => [31_400],
  ->(registers, idx) { to_string(registers, idx, 8) } => [31_413],
  ->(registers, idx) { to_string(registers, idx, 13) } => [31_421],
  ->(registers, idx) { to_string(registers, idx, 13) } => [31_434],
  ->(registers, idx) { to_string(registers, idx, 13) } => [31_447],
  ->(registers, idx) { to_string(registers, idx, 13) } => [31_460]
}.freeze
REGISTER_FORMATS =
{
  "%ds" => [1, 6, 9, 15, 84, 85, 110],
  "%dV" => [16, 112, 3331, 3424, 3523],
  # rubocop:disable Layout/MultilineArrayLineBreaks
  "%0.1f°F" => [19, 20, 401, 501, 502, 567, 740, 745, 746, 747, 900, 903,
                1109, 1110, 1111, 1112, 1113, 1114, 1124, 1125, 1134, 1135, 1136,
                3325, 3326, 3327, 3330, 3522, 3903, 3905, 3906,
                12_619, 12_620,
                21_203, 21_204,
                21_212, 21_213,
                21_221, 21_222,
                21_230, 21_231,
                21_239, 21_240,
                21_248, 21_249,
                31_003,
                31_007, 31_010, 31_013, 31_016, 31_019, 31_022],
  # rubocop:enable Layout/MultilineArrayLineBreaks
  "E%d" => [25, 26],
  "%d%%" => [282, 321, 322, 325, 346, 565, 741, 908, 1126, 3332, 3524, 3808],
  "%0.1f psi" => [419, 901, 1115, 1116, 1119, 3322, 3323],
  "%0.1fA" => [1105, 1106, 1107, 1108],
  "%0.1fgpm" => [1117],
  "%dW" => [1146, 1148, 1150, 1152, 1164, 3422],
  "%dBtuh" => [1154, 1156]
}.freeze
WRITEABLE =
[112, 340, 341, 342, 346, 347].freeze
REGISTER_RANGES =

these are the valid ranges (i.e. the ABC will return some value)

  • means 6 sequential ranges of equal size (i.e. must be repeated for each

IZ2 zone)

[
  0..155,
  170..253,
  260..260,
  280..288,
  300..301,
  320..326,
  340..348,
  360..368,
  400..419,
  440..516,
  550..573,
  600..749,
  800..913,
  1090..1165,
  1200..1263,
  2000..2026,
  2100..2129,
  2800..2849,
  2900..2915,
  2950..2959,
  3000..3003,
  3020..3030,
  3040..3049,
  3060..3063,
  3100..3105,
  3108..3115,
  3118..3119,
  3200..3253,
  3300..3332,
  3400..3431,
  3500..3524,
  3600..3609,
  3618..3634,
  3700..3714,
  3800..3809,
  3818..3834,
  3900..3914,
  12_000..12_019,
  12_098..12_099,
  12_100..12_119,
  12_200..12_239,
  12_300..12_319,
  12_400..12_569,
  12_600..12_639,
  12_700..12_799,
  20_000..20_099,
  21_100..21_136,
  21_200..21_265,
  21_400..21_472,
  21_500..21_589,
  22_100..22_162, # *
  22_200..22_262, # *
  22_300..22_362, # *
  22_400..22_462, # *
  22_500..22_562, # *
  22_600..22_662, # *
  30_000..30_099,
  31_000..31_034,
  31_100..31_129,
  31_200..31_229,
  31_300..31_329,
  31_400..31_472,
  32_100..32_162, # *
  32_200..32_262, # *
  32_300..32_362, # *
  32_400..32_462, # *
  32_500..32_562, # *
  32_600..32_662, # *
  60_050..60_053,
  60_100..60_109,
  60_200..60_200,
  61_000..61_009
].freeze
REGISTER_BREAKPOINTS =

see normalize_ranges

[
  12_100,
  12_500
].freeze
REGISTER_NAMES =
{
    0 => "Test Mode Flag", # 0x100 for enabled; this might have other flags
    1 => "Random Start Delay",
    2 => "ABC Program Version",
    3 => "??? Version?",
    4 => "DIP Switch Override",
    6 => "Compressor Anti-Short Cycle Delay",
    8 => "ABC Program Revision",
    9 => "Compressor Minimum Run Time",
    15 => "Blower Off Delay",
    16 => "Line Voltage",
    17 => "Aux/E Heat Staging Delay", # this is how long aux/eheat have been requested in seconds
    # when in eheat mode (explicit on the thermostat), it will stage up to eh2 after 130s
    # when in aux mode (thermostat set to heat; compressor at full capacity), it will stage up to eh2 after 310s
    19 => "Cooling Liquid Line Temperature (FP1)",
    20 => "Air Coil Temperature (FP2)",
    21 => "Condensate", # >= 270 normal, otherwise fault
    25 => "Last Fault Number", # high bit set if locked out
    26 => "Last Lockout",
    27 => "System Outputs (At Last Lockout)",
    28 => "System Inputs (At Last Lockout)",
    30 => "System Outputs",
    31 => "Status",
    32 => "Thermostat Input Override",
    33 => "DIP Switch Status",
    36 => "ABC Board Rev",
    45 => "Test Mode (write)", # 1 to enable
    47 => "Clear Fault History", # 0x5555 to clear
    50 => "ECM Speed Low (== 5)",
    51 => "ECM Speed Med (== 5)",
    52 => "ECM Speed High (== 5)",
    54 => "ECM Speed Actual",
    84 => "Slow Opening Water Valve Delay",
    85 => "Test Mode Timer",
    88 => "ABC Program",
    92 => "Model Number",
    105 => "Serial Number",
    110 => "Reheat Delay",
    112 => "Line Voltage Setting",
    201 => "Discharge Pressure", # I can't figure out how this number is represented;
    203 => "Suction Pressure",
    205 => "Discharge Temperature",
    207 => "Loop Entering Water Temperature",
    209 => "Compressor Ambient Temperature",
    211 => "VS Drive Details (General 1)",
    212 => "VS Drive Details (General 2)",
    213 => "VS Drive Details (Derate 1)",
    214 => "VS Drive Details (Derate 2)",
    215 => "VS Drive Details (Safemode 1)",
    216 => "VS Drive Details (Safemode 2)",
    217 => "VS Drive Details (Alarm 1)",
    218 => "VS Drive Details (Alarm 2)",
    280 => "EEV2 Ctl",
    281 => "EEV Superheat", # ?? data format
    282 => "EEV Open %",
    283 => "Suction Temperature", ## ?? data format
    284 => "Saturated Suction Temperature", ## ?? data format
    321 => "VS Pump Min",
    322 => "VS Pump Max",
    323 => "VS Pump Speed Manual Control",
    325 => "VS Pump Output",
    326 => "VS Pump Fault",
    340 => "Blower Only Speed",
    341 => "Lo Compressor ECM Speed",
    342 => "Hi Compressor ECM Speed",
    344 => "ECM Speed",
    346 => "Cooling Airflow Adjustment",
    347 => "Aux Heat ECM Speed",
    362 => "Active Dehumidify", # any value is true
    400 => "DHW Enabled",
    401 => "DHW Setpoint",
    402 => "Brine Type",
    403 => "Flow Meter Type",
    404 => "Blower Type",
    405 => "SmartGrid Trigger",
    406 => "SmartGrid Action", # 0/1 for 1/2; see 414
    407 => "Off Time Length",
    408 => "HA Alarm 1 Trigger",
    409 => "HA Alarm 1 Action",
    410 => "HA Alarm 2 Trigger",
    411 => "HA Alarm 2 Action",
    412 => "Energy Monitor", # 0 none, 1 compressor monitor, 2 energy monitor
    413 => "Pump Type",
    414 => "On Peak/SmartGrid", # 0x0001 only
    416 => "Energy Phase Type",
    417 => "Power Adjustment Factor L",
    418 => "Power Adjustment Factor H",
    419 => "Loop Pressure Trip",
    460 => "IZ2 Heartbeat?",
    461 => "IZ2 Heartbeat?",
    462 => "IZ2 Status", # 5 when online; 1 when in setup mode
    483 => "Number of IZ2 Zones",
    501 => "Set Point", # only read by AID tool? this is _not_ heating/cooling set point
    502 => "Ambient Temperature",
    564 => "IZ2 Compressor Speed Desired",
    565 => "IZ2 Blower % Desired",
    567 => "Entering Air",
    710 => "Fault Description",
    740 => "Entering Air",
    741 => "Relative Humidity",
    745 => "Heating Set Point",
    746 => "Cooling Set Point",
    747 => "Ambient Temperature", # from communicating thermostat? but set to 0 when mode is off?
    800 => "Thermostat Installed",
    801 => "Thermostat Version",
    802 => "Thermostat Revision",
    803 => "??? Installed",
    804 => "??? Version",
    805 => "??? Revision",
    806 => "AXB Installed",
    807 => "AXB Version",
    808 => "AXB Revision",
    809 => "AHB Installed",
    810 => "AHB Version",
    811 => "AHB Revision",
    812 => "IZ2 Installed",
    813 => "IZ2 Version",
    814 => "IZ2 Revision",
    815 => "AOC Installed",
    816 => "AOC Version",
    817 => "AOC Revision",
    818 => "MOC Installed",
    819 => "MOC Version",
    820 => "MOC Revision",
    824 => "EEV2 Installed",
    825 => "EEV2 Version",
    826 => "EEV2 Revision",
    827 => "AWL Installed",
    828 => "AWL Version",
    829 => "AWL Revision",
    900 => "Leaving Air",
    901 => "Suction Pressure",
    903 => "SuperHeat Temperature",
    908 => "EEV Open %",
    909 => "SubCooling (Cooling)",
    1103 => "AXB Inputs",
    1104 => "AXB Outputs",
    1105 => "Blower Amps",
    1106 => "Aux Amps",
    1107 => "Compressor 1 Amps",
    1108 => "Compressor 2 Amps",
    1109 => "Heating Liquid Line Temperature",
    1110 => "Leaving Water",
    1111 => "Entering Water",
    1112 => "Leaving Air Temperature",
    1113 => "Suction Temperature",
    1114 => "DHW Temperature",
    1115 => "Discharge Pressure",
    1116 => "Suction Pressure",
    1117 => "Waterflow",
    1119 => "Loop Pressure", # only valid < 1000psi
    1124 => "Saturated Evaporator Temperature",
    1125 => "SuperHeat",
    1126 => "Vaport Injector Open %",
    1134 => "Saturated Condensor Discharge Temperature",
    1135 => "SubCooling (Heating)",
    1136 => "SubCooling (Cooling)",
    1146 => "Compressor Watts",
    1148 => "Blower Watts",
    1150 => "Aux Watts",
    1152 => "Total Watts",
    1154 => "Heat of Extraction",
    1156 => "Heat of Rejection",
    1164 => "Pump Watts",
    # this combines thermostat/iz2 desired speed with manual operation override
    3000 => "Compressor Speed Desired",
    # this shows the actual speed
    # it can differ from desired during a ramp to the desired speed, or
    # the periodic ramp up to speed 6 that's not visible in the desired speed
    3001 => "Compressor Speed Actual",
    3002 => "Manual Operation",
    3027 => "Compressor Speed",
    3220 => "VS Drive Details (General 1)",
    3221 => "VS Drive Details (General 2)",
    3222 => "VS Drive Details (Derate 1)",
    3223 => "VS Drive Details (Derate 2)",
    3224 => "VS Drive Details (Safemode 1)",
    3225 => "VS Drive Details (Safemode 2)",
    3226 => "VS Drive Details (Alarm 1)",
    3227 => "VS Drive Details (Alarm 2)",
    3322 => "VS Drive Discharge Pressure",
    3323 => "VS Drive Suction Pressure",
    3325 => "VS Drive Discharge Temperature",
    3326 => "VS Drive Compressor Ambient Temperature",
    3327 => "VS Drive Temperature",
    3330 => "VS Drive Entering Water Temperature",
    3331 => "VS Drive Line Voltage",
    3332 => "VS Drive Thermo Power",
    3422 => "VS Drive Compressor Power",
    3424 => "VS Drive Supply Voltage",
    3522 => "VS Drive Inverter Temperature",
    3523 => "VS Drive UDC Voltage",
    3524 => "VS Drive Fan Speed",
    3804 => "VS Drive Details (EEV2 Ctl)",
    3808 => "VS Drive EEV2 % Open",
    3903 => "VS Drive Suction Temperature",
    3904 => "VS Drive Leaving Air Temperature?",
    3905 => "VS Drive Saturated Evaporator Discharge Temperature",
    3906 => "VS Drive SuperHeat Temperature",
    12_005 => "Fan Configuration",
    12_006 => "Heating Mode",
    12_309 => "De/Humidifier Mode",
    12_310 => "De/Humidifier Setpoints",
    12_606 => "Heating Mode (write)",
    12_619 => "Heating Setpoint (write)",
    12_620 => "Cooling Setpoint (write)",
    12_621 => "Fan Mode (write)",
    12_622 => "Intermittent Fan On Time (write)",
    12_623 => "Intermittent Fan Off Time (write)",
    21_114 => "IZ2 De/Humidifier Mode (write)",
    21_115 => "IZ2 De/Humidifier Setpoints (write)",
    31_003 => "Outdoor Temp",
    31_005 => "IZ2 Demand",
    31_109 => "De/Humidifier Mode",
    31_110 => "Manual De/Humidification Setpoints",
    31_400 => "Dealer Name",
    31_413 => "Dealer Phone",
    31_421 => "Dealer Address 1",
    31_434 => "Dealer Address 2",
    31_447 => "Dealer Email",
    31_460 => "Dealer Website"
  }.merge(ignore(89..91))
.merge(ignore(93..104))
.merge(ignore(106..109))
.merge(faults(601..699))
.merge(ignore(711..717))
.merge(zone_registers)
.merge(ignore(1147, 1149, 1151, 1153, 1155, 1157, 1165))
.merge(ignore(3423, 3425))
.merge(ignore(31_401..31_412))
.merge(ignore(31_414..31_420))
.merge(ignore(31_422..31_433))
.merge(ignore(31_435..31_446))
.merge(ignore(31_447..31_459))
.merge(ignore(31_461..31_472))

Class Attribute Summary collapse

Class Method Summary collapse

Class Attribute Details

.loggerObject

Returns the value of attribute logger.



19
20
21
# File 'lib/aurora.rb', line 19

def logger
  @logger
end

Class Method Details

.axb_inputs(value) ⇒ Object



312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
# File 'lib/aurora/registers.rb', line 312

def axb_inputs(value)
  result = {}
  result[:smart_grid] = value & 0x001 == 0x001
  result[:ha1] = value & 0x002 == 0x002
  result[:ha2] = value & 0x004 == 0x004
  result[:pump_slave] = value & 0x008 == 0x008

  result[:mb_address] = (value & 0x010 == 0x010) ? 3 : 4
  result[:sw1_2] = value & 0x020 == 0x020 # future use # rubocop:disable Naming/VariableNumber
  result[:sw1_3] = value & 0x040 == 0x040 # future use # rubocop:disable Naming/VariableNumber
  result[:accessory_relay2] = if value & 0x080 == 0x080 && value & 0x100 == 0x100
                                :blower
                              elsif value & 0x100 == 0x100
                                :low_capacity_compressor
                              elsif value & 0x080 == 0x080
                                :high_capacity_compressor
                              else
                                :dehumidifier
                              end
  leftover = value & ~0x1ff
  result[:unknown] = format("0x%04x", leftover) unless leftover.zero?
  result
end

.diff_registers(lhs, rhs) ⇒ Object



968
969
970
971
972
973
974
# File 'lib/aurora/registers.rb', line 968

def diff_registers(lhs, rhs)
  diff = {}
  (lhs.keys | rhs.keys).each do |k|
    diff[k] = rhs[k] if lhs[k] != rhs[k]
  end
  diff
end

.dipswitch_settings(value) ⇒ Object



211
212
213
214
215
216
217
218
219
220
221
222
223
# File 'lib/aurora/registers.rb', line 211

def dipswitch_settings(value)
  return :manual if value == 0x7fff

  {
    fp1: (value & 0x01 == 0x01) ? 30 : 15,
    fp2: (value & 0x02 == 0x02) ? 30 : :off,
    reversing_valve: (value & 0x04 == 0x04) ? :o : :b, # cycle to cool on O, or !B
    accessory_relay: ACCESSORY_RELAY_SETTINGS[(value >> 3) & 0x3],
    compressor: (value & 0x20 == 0x20) ? 1 : 2, # single or dual stage compressor
    lockout: (value & 0x40 == 0x40) ? :continuous : :pulse,
    dehumidifier_reheat: (value & 0x80 == 0x80) ? :dehumidifier : :reheat
  }
end

.faults(range) ⇒ Object



595
596
597
598
599
600
601
# File 'lib/aurora/registers.rb', line 595

def faults(range)
  range.map do |i|
    name = FAULTS[i % 100]
    name = " (#{name})" if name
    [i, "E#{i % 100}#{name}"]
  end.to_h
end

.from_bitmask(value, flags) ⇒ Object



53
54
55
56
57
58
59
60
61
# File 'lib/aurora/registers.rb', line 53

def from_bitmask(value, flags)
  result = []
  flags.each do |(bit, flag)|
    result << flag if value & bit == bit
    value &= ~bit
  end
  result << format("0x%04x", value) unless value.zero?
  result
end

.ignore(*range) ⇒ Object



589
590
591
592
593
# File 'lib/aurora/registers.rb', line 589

def ignore(*range)
  range = range.first if range.length == 1
  range = [range] if range.is_a?(Integer)
  range.zip(Array.new(range.count)).to_h
end

.iz2_demand(value) ⇒ Object



413
414
415
416
417
418
# File 'lib/aurora/registers.rb', line 413

def iz2_demand(value)
  {
    fan_demand: value >> 8,
    unit_demand: value & 0xff
  }
end

.iz2_fan_desired(value) ⇒ Object



420
421
422
423
424
425
426
427
428
429
430
# File 'lib/aurora/registers.rb', line 420

def iz2_fan_desired(value)
  case value
  when 1 then 25
  when 2 then 40
  when 3 then 55
  when 4 then 70
  when 5 then 85
  when 6 then 100
  else value
  end
end

.manual_operation(value) ⇒ Object



386
387
388
389
390
391
392
393
394
395
396
397
398
399
# File 'lib/aurora/registers.rb', line 386

def manual_operation(value)
  return :off if value == 0x7fff

  result = {
    mode: (value & 0x100 == 0x100) ? :cooling : :heating
  }
  result[:aux_heat] = true if value & 0x200 == 0x200
  result[:compressor_speed] = value & 0xf
  result[:blower_speed] = value & 0xf0
  result[:blower_speed] = :with_compressor if value & 0xf0 == 0xf0
  leftover = value & ~0x03ff
  result[:unknown] = format("0x%04x", leftover) unless leftover.zero?
  result
end

.normalize_ranges(ranges) ⇒ Object

take an array of ranges, and breaks it up into queryable chunks the ABC limits to 100 registers per read operation there also seem to be issues that some ranges can’t be read at the same time as other ranges. possibly correspond to different components?



11
12
13
14
15
16
17
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
# File 'lib/aurora/registers.rb', line 11

def normalize_ranges(ranges)
  registers = ranges.map { |r| Array(r) }.flatten.sort.uniq
  result = []
  totals = []
  run_start = nil
  count = 0
  registers.each_with_index do |r, i|
    run_start ||= r
    next unless i + 1 == registers.length ||
                r + 1 != registers[i + 1] ||
                (r - run_start) == 100 ||
                REGISTER_BREAKPOINTS.include?(r + 1)

    if r == run_start
      result << r
      if (count += 1) == 100
        totals << result
        result = []
        count = 0
      end
    else
      range = run_start..r
      if count + range.count > 100
        totals << result unless result.empty?
        result = []
        count = 0
      end
      count += range.count
      result << range
    end
    run_start = nil
  end
  totals << result unless result.empty?
  totals
end


976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
# File 'lib/aurora/registers.rb', line 976

def print_registers(registers)
  result = [] unless block_given?
  registers.each do |(k, value)|
    # ignored
    next if REGISTER_NAMES.key?(k) && REGISTER_NAMES[k].nil?

    name = REGISTER_NAMES[k]

    value_proc = REGISTER_CONVERTERS.find { |(_, z)| z.include?(k) }&.first || ->(v) { v }
    format = REGISTER_FORMATS.find { |(_, z)| z.include?(k) }&.first || "%s"
    format = "%1$d (0x%1$04x)" unless name

    value = (value_proc.arity == 2) ? value_proc.call(registers, k) : value_proc.call(value)
    value = value.join(", ") if value.is_a?(Array)
    value = format(format, value) if value

    name ||= "???"

    full_value = "#{name} (#{k}): #{value}"
    if block_given?
      yield(k, full_value)
    else
      result << full_value
    end
  end
  result.join("\n") unless block_given?
end

.read_all_registers(modbus_slave) ⇒ Object



957
958
959
960
961
962
963
964
965
966
# File 'lib/aurora/registers.rb', line 957

def read_all_registers(modbus_slave)
  result = []
  REGISTER_RANGES.each do |range|
    # read at most 100 at a time
    range.each_slice(100) do |keys|
      result.concat(modbus_slave.holding_registers[keys.first..keys.last])
    end
  end
  REGISTER_RANGES.map(&:to_a).flatten.zip(result).to_h
end

.status(value) ⇒ Object



257
258
259
260
261
262
263
264
265
266
267
268
# File 'lib/aurora/registers.rb', line 257

def status(value)
  result = {
    lps: (value & 0x80 == 0x80) ? :closed : :open,
    hps: (value & 0x100 == 0x100) ? :closed : :open
  }
  SYSTEM_INPUTS.each do |(i, name)|
    result[name] = true if value & i == i
  end
  leftover = value & ~0x03ff
  result[:unknown] = format("0x%04x", leftover) unless leftover.zero?
  result
end

.thermostat_configuration2(value) ⇒ Object



432
433
434
435
436
437
438
439
# File 'lib/aurora/registers.rb', line 432

def thermostat_configuration2(value)
  result = {
    mode: HEATING_MODE[(value >> 8) & 0x07]
  }
  leftover = value & ~0x0700
  result[:unknown] = format("0x%04x", leftover) unless leftover.zero?
  result
end

.thermostat_override(value) ⇒ Object



407
408
409
410
411
# File 'lib/aurora/registers.rb', line 407

def thermostat_override(value)
  return [:off] if value == 0x7fff

  from_bitmask(value, SYSTEM_INPUTS)
end

.to_int32(registers, idx) ⇒ Object



68
69
70
71
# File 'lib/aurora/registers.rb', line 68

def to_int32(registers, idx)
  v = to_uint32(registers, idx)
  (v & 0x80000000 == 0x80000000) ? v - 0x100000000 : v
end

.to_string(registers, idx, length) ⇒ Object



73
74
75
76
77
78
79
# File 'lib/aurora/registers.rb', line 73

def to_string(registers, idx, length)
  (idx...(idx + length)).map do |i|
    next "\ufffd" unless registers[i] # missing data? add unicode invalid character

    (registers[i] >> 8).chr + (registers[i] & 0xff).chr
  end.join.sub(/[ \0]+$/, "")
end

.to_uint32(registers, idx) ⇒ Object



63
64
65
66
# File 'lib/aurora/registers.rb', line 63

def to_uint32(registers, idx)
  Aurora.logger&.warn("Missing register #{idx + 1}") unless registers[idx + 1]
  (registers[idx] << 16) + registers[idx + 1]
end

.transform_registers(registers) ⇒ Object



946
947
948
949
950
951
952
953
954
955
# File 'lib/aurora/registers.rb', line 946

def transform_registers(registers)
  registers.each do |(k, v)|
    value_proc = REGISTER_CONVERTERS.find { |(_, z)| z.include?(k) }&.first
    next unless value_proc

    value = (value_proc.arity == 2) ? value_proc.call(registers, k) : value_proc.call(v)
    registers[k] = value
  end
  registers
end

.vs_fault(value) ⇒ Object



199
200
201
202
# File 'lib/aurora/registers.rb', line 199

def vs_fault(value)
  name = VS_FAULTS.find { |(_, range)| range.include?(value) }&.first
  name ? "#{value} #{name}" : value.to_s
end

.vs_manual_control(value) ⇒ Object



401
402
403
404
405
# File 'lib/aurora/registers.rb', line 401

def vs_manual_control(value)
  return :off if value == 0x7fff

  value
end

.zone_configuration1(value) ⇒ Object



441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
# File 'lib/aurora/registers.rb', line 441

def zone_configuration1(value)
  fan = if value & 0x80 == 0x80
          :continuous
        elsif value & 0x100 == 0x100
          :intermittent
        else
          :auto
        end
  result = {
    fan: fan,
    on_time: ((value >> 9) & 0x7) * 5,
    off_time: (((value >> 12) & 0x7) + 1) * 5,
    cooling_target_temperature: ((value & 0x7e) >> 1) + 36,
    heating_target_temperature_carry: value & 0x01
  }
  leftover = value & ~0x7fff
  result[:unknown] = format("0x%04x", leftover) unless leftover.zero?
  result
end

.zone_configuration2(registers, key) ⇒ Object



461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
# File 'lib/aurora/registers.rb', line 461

def zone_configuration2(registers, key)
  prior_v = registers[key - 1] if registers.key?(key - 1)
  v = registers[key]
  result = {
    call: CALLS[(v >> 1) & 0x7],
    mode: HEATING_MODE[(v >> 8) & 0x03],
    damper: (v & 0x10 == 0x10) ? :open : :closed
  }
  if prior_v
    carry = prior_v.is_a?(Hash) ? prior_v[:heating_target_temperature_carry] : v & 0x01
    result[:heating_target_temperature] = ((carry << 5) | ((v & 0xf800) >> 11)) + 36
  end
  leftover = v & ~0xfb1e
  result[:unknown] = format("0x%04x", leftover) unless leftover.zero?
  result
end

.zone_configuration3(value) ⇒ Object

hi order byte is normalized zone size



479
480
481
482
483
484
485
486
487
488
489
# File 'lib/aurora/registers.rb', line 479

def zone_configuration3(value)
  size = (value >> 3) & 0x3
  result = {
    zone_priority: value.allbits?(0x20) ? :economy : :comfort,
    zone_size: ZONE_SIZES[size],
    normalized_size: value >> 8
  }
  leftover = value & ~0xff38
  result[:unknown] = format("0x%04x", leftover) unless leftover.zero?
  result
end

.zone_registersObject



603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
# File 'lib/aurora/registers.rb', line 603

def zone_registers
  (1..6).map do |i|
    base1 = 21_202 + ((i - 1) * 9)
    base2 = 31_007 + ((i - 1) * 3)
    base3 = 31_200 + ((i - 1) * 3)
    {
      base1 => "Zone #{i} Heating Mode (write)",
      (base1 + 1) => "Zone #{i} Heating Setpoint (write)",
      (base1 + 2) => "Zone #{i} Cooling Setpoint (write)",
      (base1 + 3) => "Zone #{i} Fan Mode (write)",
      (base1 + 4) => "Zone #{i} Intermittent Fan On Time (write)",
      (base1 + 5) => "Zone #{i} Intermittent Fan Off Time (write)",
      base2 => "Zone #{i} Ambient Temperature",
      (base2 + 1) => "Zone #{i} Configuration 1",
      (base2 + 2) => "Zone #{i} Configuration 2",
      base3 => "Zone #{i} Configuration 3"
    }
  end.inject({}, &:merge)
end