Modbus heatpump issue configuration Esphome

I have a heat pump boiler with modbus protocol but I can’t communicate with it, I attach my configuration: esphome side:
`esphome:

  name: esphome-web-f96b62
  friendly_name: ESPHome Web f96b62

esp8266:
  board: esp01_1m

# Enable logging
logger:

# Enable Home Assistant API
api:
  encryption:
    key: "XXXXXXXXXXXXXXAlvDLbWdJr3BxhnxchQCfMSQEDrBXPAZBnqs="

ota:


wifi:
  ssid: "XXXXXXXXXXXX"
  password: "XXXXXXXXXXXXXX"

  # Enable fallback hotspot (captive portal) in case wifi connection fails
  ap:
    ssid: "XXXXXXXXXXXX"
    password: "XXXXXXXXXXXX"

captive_portal:

uart:
  id: mod_bus
  tx_pin: GPIO1
  rx_pin: GPIO3
  baud_rate: 9600
  stop_bits: 1
  #baud_rate: 115200
  
modbus:
  id: modbus1
  uart_id: mod_bus
  #send_wait_time: 200ms
  
modbus_controller:
  id: vlb
# the Modbus device addr
  address: 0x01
  modbus_id: modbus1
  update_interval: 5s
  #setup_priority: -10  

sensor:
  - platform: modbus_controller
    modbus_controller_id: vlb
    name: "maxa"
    id: maxa
    register_type: holding
    address: 2001
    value_type: U_WORD`

configuration.yaml:

modbus:
  - name: modbus1
    type: tcp
    host: 192.168.1.6
    port: 502

these are the moddbus addresses of the machine

error no response…

I know it’s been a while since you posted, but I managed to connect using an ESP32 with an RS485 module and set up the following configuration in case someone else needs it.

uart:
  tx_pin: GPIO1
  rx_pin: GPIO3
  baud_rate: 9600
  parity: EVEN
  stop_bits: 1
  data_bits: 8

modbus:
  id: modbus1
  

modbus_controller:
  - id: heat_pump_modbus
    address: 0x01  ## address of the ModBUS slave device on the bus
    modbus_id: modbus1
    setup_priority: -10
    update_interval: 10s
    command_throttle: 2ms
1 Like

thanks for the reply, I only saw it now, I’ll try over the weekend, I wanted to ask you again, I have to replace this part of the code you sent with my parameters, in the sense: I have to leave (sensor:) etc. ?

yes, the sensors and other entities remain the same. The code above is only for connecting to the modbus controller.

1 Like

Here is an example for reading Alarms:

sensors:
### ALARMS
  - platform: modbus_controller
    modbus_controller_id: heat_pump_modbus
    name: "HP Status"
    address: 0x00C8
    value_type: U_WORD
    register_type: holding
    id: alarm1_raw
    on_value:
      then:
        - lambda: |-
            std::string state;
            switch ((int)id(alarm1_raw).state) {  // Cast to integer
              case 0:
                state = "High pressure - E001";
                break;
              case 1:
                state = "Low pressure - E002";
                break;
              case 2:
                state = "Compressor thermal protection - E003";
                break;
              case 3:
                state = "Fan theral protection - E004";
                break;
              case 4:
                state = "Frost - E005";
                break;
              case 5:
                state = "Lack of flow - E006";
                break;
              case 6:
                state = "ACC prepare low temperature - E007";
                break;
              case 7:
                state = "Lack of librication - E008";
                break;
              case 8:
                state = "High discharge temeperature of Cp 1 - E009";
                break;
              case 9:
                state = "Solar collector at high temperature - E010";
                break;
              case 12:
                state = "Compressor 2 thermal protection - E013";
                break;
              case 13:
                state = "Fan 2 thermal protection - E014";
                break;
              case 15:
                state = "Pump thermal protection - E016";
                break;
              default:
                state = "Unknown";
            }
            id(alarm1).publish_state(state);
1 Like

Hi @gojonny i’m about to install an i-32V5 heat pump soon, can i ask you where did you find the modbus reference? are you able to share it? if you have the HA implementation working and want to share that too it would be amazing

I didn’t succeed, I thought I had found something on the net but it wasn’t what I needed, someone very kind tried to help me but nothing, so I abandoned the project I even spent a lot of money buying a hs-2211 converter module but nothing I didn’t succeed, I hope that someone can in the future because I need to remotely control my heat pump.

Thank you, if i eventually do something I’ll post an update

Hey, i finally moved in my new home and had time messing with this. I am happy to report that i managed to get (almost) everything working, Here’s a rundown of what i did and the needed config.

oh boy was I in for a ride…
First of all: you can’t connect both the original heatpump remote control and the rs485 interface together,m since modbus is a one-master protocol, so you will need to either:

  • remove the maxa remote panel and only connect your 485 bridge
  • buy a multi-master 485 hub.

I tried getting an hub to retain all the original functionality but was not too lucky the first time.

The hub i bought (the cheapest i found on aliexpress, duh) was a YR-8103 module: After days and days of trying to understand how it works, trying every possible comibation of cabling and software configuration, and trying to deal with the seller (which was a fucking mess) I concluded that it’s not compatible with the maxa modbus, as far as i was able to gather that’s because it doesn’t support configuration of stop bit.
DO NOT buy the YR-8103 rs485 multi master module. it sucks, the software is not available in english, and it has an half assed implementation of the protocol.

The second time (i went with the second most cheaper this time, 5head) i bought a GC-1201s module, and this time i was fortunate enough, the module is able to relay messages from both the original panel and the 485 bridge to the heatpump.
Again, the sellers of this module are not too helpful, but the configuration software (which is detected as a virus by windows, but i think that’s just bad programming) at least has an “english” button which gets you an english interface. The manual is also not available in english, but i’ll attach a machine translated version of it.
The config is pretty single anyways, you just have to set:

  • baud: 9600
  • data bits: 8
  • parity: even
  • stop bits: 1

for all the ports on the module (2 masters + 1 slave)

Now, for everything else.
i used an elfin EW11 to read data from the heatpump, configured like this:


i then used the modbus integration in homeassistant to create sensors and controls for almost everything listed in the spec in the manual, what i tested works, but i’m sure it can be made better, i’m happy to try a revised version if anyone tries!

modbus:
  - name: maxa_rtu_gateway
    type: tcp
    host: 192.168.22.6 # Elfin gateway IP
    port: 502
    timeout: 5
    delay: 1
    message_wait_milliseconds: 200

    ##############################################################
    # SENSORS – READ-ONLY VALUES                                #
    ##############################################################
    sensors:

      # firmware registers cause errors, not essential anyways
      # # Firmware / identification
      # - name: "Firmware Version"
      #   slave: 1
      #   address: 1
      #   input_type: holding
      #   data_type: uint16

      # - name: "Firmware Release"
      #   slave: 1
      #   address: 2
      #   input_type: holding
      #   data_type: uint16

      # - name: "Firmware Sub-Release"
      #   slave: 1
      #   address: 3
      #   input_type: holding
      #   data_type: uint16

      # Serial number (ASCII, 18 × 2-byte holding = 36 chars)
      - name: "Serial Number"
        slave: 1
        address: 80
        input_type: holding
        data_type: string
        count: 18

      # Flow-meter
      - name: "Water Flow Rate"
        slave: 1
        address: 444
        input_type: holding
        data_type: uint16
        unit_of_measurement: "L/min"
        # icon: mdi:water

      # Temperatures (°C, scale 0.1)
      - name: "Water Inlet Temp"
        slave: 1
        address: 400
        input_type: holding
        data_type: int16
        scale: 0.1
        precision: 1
        unit_of_measurement: "°C"

      - name: "Water Outlet Temp"
        slave: 1
        address: 401
        input_type: holding
        data_type: int16
        scale: 0.1
        precision: 1
        unit_of_measurement: "°C"

      - name: "DHW Temp"
        slave: 1
        address: 405
        input_type: holding
        data_type: int16
        scale: 0.1
        precision: 1
        unit_of_measurement: "°C"

      - name: "Comp Inhalation Temp"
        slave: 1
        address: 422
        input_type: holding
        data_type: int16
        scale: 0.1
        precision: 1
        unit_of_measurement: "°C"

      - name: "Outdoor Temp"
        slave: 1
        address: 428
        input_type: holding
        data_type: int16
        scale: 0.1
        precision: 1
        unit_of_measurement: "°C"

      - name: "Comp Discharge 1 Temp"
        slave: 1
        address: 433
        input_type: holding
        data_type: int16
        scale: 0.1
        precision: 1
        unit_of_measurement: "°C"

      - name: "Comp Discharge 2 Temp"
        slave: 1
        address: 434
        input_type: holding
        data_type: int16
        scale: 0.1
        precision: 1
        unit_of_measurement: "°C"

      - name: "Comp Discharge 3 Temp"
        slave: 1
        address: 435
        input_type: holding
        data_type: int16
        scale: 0.1
        precision: 1
        unit_of_measurement: "°C"

      - name: "Solar Collector Temp"
        slave: 1
        address: 437
        input_type: holding
        data_type: int16
        scale: 0.1
        precision: 1
        unit_of_measurement: "°C"

      - name: "Solar Accumulation Temp"
        slave: 1
        address: 438
        input_type: holding
        data_type: int16
        scale: 0.1
        precision: 1
        unit_of_measurement: "°C"

      - name: "Plant Remote Temp"
        slave: 1
        address: 440
        input_type: holding
        data_type: int16
        scale: 0.1
        precision: 1
        unit_of_measurement: "°C"

      - name: "Radiant Panels Mix Delivery Temp"
        slave: 1
        address: 443
        input_type: holding
        data_type: int16
        scale: 0.1
        precision: 1
        unit_of_measurement: "°C"

      - name: "DHW Preparer Recirc Temp"
        slave: 1
        address: 447
        input_type: holding
        data_type: int16
        scale: 0.1
        precision: 1
        unit_of_measurement: "°C"

      - name: "Evaporation Temp"
        slave: 1
        address: 253
        input_type: holding
        data_type: int16
        scale: 0.1
        precision: 1
        unit_of_measurement: "°C"

      - name: "Condensation Temp"
        slave: 1
        address: 254
        input_type: holding
        data_type: int16
        scale: 0.1
        precision: 1
        unit_of_measurement: "°C"

      - name: "Evaporation Temp-C2"
        slave: 1
        address: 626
        input_type: holding
        data_type: int16
        scale: 0.1
        precision: 1
        unit_of_measurement: "°C"

      - name: "Condensation Temp-C2"
        slave: 1
        address: 627
        input_type: holding
        data_type: int16
        scale: 0.1
        precision: 1
        unit_of_measurement: "°C"

      - name: "Comp Inhalation Temp-C2"
        slave: 1
        address: 20422
        input_type: holding
        data_type: int16
        scale: 0.1
        precision: 1
        unit_of_measurement: "°C"

      - name: "Comp1 Discharge Temp-C2"
        slave: 1
        address: 20433
        input_type: holding
        data_type: int16
        scale: 0.1
        precision: 1
        unit_of_measurement: "°C"

      - name: "Comp2 Discharge Temp-C2"
        slave: 1
        address: 20434
        input_type: holding
        data_type: int16
        scale: 0.1
        precision: 1
        unit_of_measurement: "°C"

      - name: "Comp3 Discharge Temp-C2"
        slave: 1
        address: 20435
        input_type: holding
        data_type: int16
        scale: 0.1
        precision: 1
        unit_of_measurement: "°C"

      # Pressures (bar, scale 0.01)
      - name: "High Pressure"
        slave: 1
        address: 406
        input_type: holding
        data_type: int16
        scale: 0.01
        precision: 2
        unit_of_measurement: "bar"

      - name: "Low Pressure"
        slave: 1
        address: 414
        input_type: holding
        data_type: int16
        scale: 0.01
        precision: 2
        unit_of_measurement: "bar"

      - name: "High Pressure-C2"
        slave: 1
        address: 20406
        input_type: holding
        data_type: int16
        scale: 0.01
        precision: 2
        unit_of_measurement: "bar"

      - name: "Low Pressure-C2"
        slave: 1
        address: 20414
        input_type: holding
        data_type: int16
        scale: 0.01
        precision: 2
        unit_of_measurement: "bar"

      # Analogue outputs (% , scale 0.1)
      - name: "Condensation Fan %"
        slave: 1
        address: 7000
        input_type: holding
        data_type: int16
        scale: 0.1
        precision: 1
        unit_of_measurement: "%"

      - name: "Circulating Pump %"
        slave: 1
        address: 7001
        input_type: holding
        data_type: int16
        scale: 0.1
        precision: 1
        unit_of_measurement: "%"

      - name: "Condensation Fan %-C2"
        slave: 1
        address: 628
        input_type: holding
        data_type: int16
        scale: 0.1
        precision: 1
        unit_of_measurement: "%"

      # Operating hours (h)
      - name: "Compressor 1 Hours"
        slave: 1
        address: 305
        input_type: holding
        data_type: int16
        unit_of_measurement: "h"

      - name: "Compressor 2 Hours"
        slave: 1
        address: 307
        input_type: holding
        data_type: int16
        unit_of_measurement: "h"

      - name: "Compressor 3 Hours"
        slave: 1
        address: 309
        input_type: holding
        data_type: int16
        unit_of_measurement: "h"

      - name: "Compressor 1-C2 Hours"
        slave: 1
        address: 313
        input_type: holding
        data_type: int16
        unit_of_measurement: "h"

      - name: "Compressor 2-C2 Hours"
        slave: 1
        address: 315
        input_type: holding
        data_type: int16
        unit_of_measurement: "h"

      - name: "Compressor 3-C2 Hours"
        slave: 1
        address: 317
        input_type: holding
        data_type: int16
        unit_of_measurement: "h"

      # Machine status register (200)
      - name: "Machine Status Raw"
        slave: 1
        address: 200
        input_type: holding
        data_type: uint16

      - name: "Alarm Register 950"
        slave: 1
        address: 950
        input_type: holding
        data_type: uint16
      - name: "Alarm Register 951"
        slave: 1
        address: 951
        input_type: holding
        data_type: uint16
      - name: "Alarm Register 952"
        slave: 1
        address: 952
        input_type: holding
        data_type: uint16
      - name: "Alarm Register 953"
        slave: 1
        address: 953
        input_type: holding
        data_type: uint16
      - name: "Alarm Register 954"
        slave: 1
        address: 954
        input_type: holding
        data_type: uint16
      - name: "Alarm Register 955"
        slave: 1
        address: 955
        input_type: holding
        data_type: uint16
      - name: "Alarm Register 956"
        slave: 1
        address: 956
        input_type: holding
        data_type: uint16

    ##############################################################
    # CLIMATES – SETPOINTS (READ/WRITE)                          #
    ##############################################################
    climates:
      # Each climate entity exposes a single setpoint register as a thermostat.

      - name: "Cooling Setpoint"
        slave: 1
        address: 7203
        input_type: holding
        data_type: int16
        scale: 0.1
        precision: 1
        min_temp: 5.0
        max_temp: 23.0
        temp_step: 0.1
        target_temp_register: 7203
        temperature_unit: C

      - name: "Heating Setpoint"
        slave: 1
        address: 7204
        input_type: holding
        data_type: int16
        scale: 0.1
        precision: 1
        min_temp: 25.0
        max_temp: 55.0
        temp_step: 0.1
        target_temp_register: 7204
        temperature_unit: C

      - name: "Sanitary Setpoint"
        slave: 1
        address: 7205
        input_type: holding
        data_type: int16
        scale: 0.1
        precision: 1
        min_temp: 25.0
        max_temp: 55.0
        temp_step: 0.1
        target_temp_register: 7205
        temperature_unit: C

      - name: "Second Cooling Setpoint"
        slave: 1
        address: 7206
        input_type: holding
        data_type: int16
        scale: 0.1
        precision: 1
        min_temp: 5.0
        max_temp: 23.0
        temp_step: 0.1
        target_temp_register: 7206
        temperature_unit: C

      - name: "Second Heating Setpoint"
        slave: 1
        address: 7207
        input_type: holding
        data_type: int16
        scale: 0.1
        precision: 1
        min_temp: 25.0
        max_temp: 55.0
        temp_step: 0.1
        target_temp_register: 7207
        temperature_unit: C

      - name: "DHW Preparer Setpoint"
        slave: 1
        address: 7208
        input_type: holding
        data_type: int16
        scale: 0.1
        precision: 1
        min_temp: 0.0
        max_temp: 80.0
        temp_step: 0.1
        target_temp_register: 7208
        temperature_unit: C

      # MACHINE MODE via climate entity (register 7200)
      - name: "Heat Pump Mode"
        slave: 1
        address: 400 # water inlet temp as current temp feedback
        input_type: holding
        data_type: int16
        scale: 0.1
        precision: 1
        temperature_unit: C
        hvac_mode_register:
          address: 7200
          values:
            state_off: 0 # Standby
            state_cool: 1 # Cooling
            state_heat: 2 # Heating
        target_temp_register: 7203 # primary cooling setpoint for UI control
        min_temp: 5.0
        max_temp: 55.0
        temp_step: 0.1

    ##############################################################
    # SWITCHES – REMOTE ENABLE / SPECIAL REQUESTS                #
    ##############################################################
    switches:
      - name: "Remote State Write Enable"
        slave: 1
        address: 7201
        write_type: holding
        command_on: 1 # bit 0 => value 1
        command_off: 0
        verify:
          input_type: holding
          delay: 1

      - name: "Remote Setpoint Write Enable"
        slave: 1
        address: 7201
        write_type: holding
        command_on: 2 # bit 1 => value 2
        command_off: 0
        verify:
          input_type: holding
          delay: 1

      # 7201 – Enable bits
      - name: "Second Setpoint Enable"
        slave: 1
        address: 7201
        write_type: holding
        command_on: 4 # bit 2 => value 4
        command_off: 0
        verify:
          input_type: holding
          delay: 1

      - name: "Room Temp Call Enable"
        slave: 1
        address: 7201
        write_type: holding
        command_on: 8 # bit 3 => value 8
        command_off: 0
        verify:
          input_type: holding
          delay: 1

      - name: "Sanitary Call Enable"
        slave: 1
        address: 7201
        write_type: holding
        command_on: 16 # bit 4 => value 16
        command_off: 0
        verify:
          input_type: holding
          delay: 1

      - name: "Anti-Legionella Enable"
        slave: 1
        address: 7201
        write_type: holding
        command_on: 32 # bit 5 => value 32
        command_off: 0
        verify:
          input_type: holding
          delay: 1

      # 7202 – Remote commands / requests
      - name: "Second Setpoint Active"
        slave: 1
        address: 7202
        write_type: holding
        command_on: 1 # bit 0
        command_off: 0
        verify:
          input_type: holding
          delay: 1

      - name: "Room Temp Call"
        slave: 1
        address: 7202
        write_type: holding
        command_on: 2 # bit 1
        command_off: 0
        verify:
          input_type: holding
          delay: 1

      - name: "Sanitary Call"
        slave: 1
        address: 7202
        write_type: holding
        command_on: 4 # bit 2
        command_off: 0
        verify:
          input_type: holding
          delay: 1

      - name: "Anti-Legionella Cycle Request"
        slave: 1
        address: 7202
        write_type: holding
        command_on: 8 # bit 3
        command_off: 0
        verify:
          input_type: holding
          delay: 1

      - name: "Plant Air-Vent"
        slave: 1
        address: 7202
        write_type: holding
        command_on: 32 # bit 5
        command_off: 0
        verify:
          input_type: holding
          delay: 1

      - name: "Sanitary Disabling"
        slave: 1
        address: 7202
        write_type: holding
        command_on: 64 # bit 6
        command_off: 0
        verify:
          input_type: holding
          delay: 1

      - name: "Forced Defrosting"
        slave: 1
        address: 7202
        write_type: holding
        command_on: 128 # bit 7
        command_off: 0
        verify:
          input_type: holding
          delay: 1

    ##############################################################
    # CLIMATE (OPTIONAL)                                         #
    ##############################################################
    # Home Assistant’s generic_modbus climate platform can wrap
    # the above setpoint numbers and a mode switch. This is left
    # out to keep the YAML concise – create a climate entity in
    # UI using the Helper → Generic Thermostat if needed.

# -------------------------------------------------------------------
# TEMPLATE SENSORS
# -------------------------------------------------------------------

template:
  - sensor:
      - name: "Current Alarm"
        state: >
          {% set regs = [
            states('sensor.alarm_register_950')|int,
            states('sensor.alarm_register_951')|int,
            states('sensor.alarm_register_952')|int,
            states('sensor.alarm_register_953')|int,
            states('sensor.alarm_register_954')|int,
            states('sensor.alarm_register_955')|int,
            states('sensor.alarm_register_956')|int
          ] %}
          {% set alarm_map = [
            (0, 1, "High Pressure"),
            (0, 2, "Low Pressure"),
            (0, 4, "Compressor Thermal"),
            (0, 8, "Fan Thermal"),
            (0, 16, "Frost"),
            (0, 32, "Lack of Flow"),
            (0, 64, "DHW Low Temperature"),
            (0, 128, "Lack of Lubrication"),
            (0, 256, "High Discharge Temp Cp1"),
            (0, 512, "Solar Collector High Temp"),
            (0, 4096, "Compressor 2 Thermal"),
            (0, 8192, "Fan 2 Thermal"),
            (0, 32768, "Pump Thermal"),
            (1, 2, "High Temperature"),
            (1, 4, "High Discharge Temp Cp2"),
            (1, 8, "Inverted Pressure Transducers"),
            (1, 64, "Compressor 3 Thermal"),
            (1, 128, "Fan 3 Thermal"),
            (1, 512, "Pump 2 Thermal"),
            (1, 2048, "Incongruent Temperatures"),
            (1, 4096, "Poor Heat Exchange DHW"),
            (1, 8192, "DHW Accumulation Tank High Temp"),
            (1, 16384, "I/O Module 1 Disconnected"),
            (1, 32768, "I/O Module 2 Disconnected"),
            (2, 1, "Probe 1 Error"),
            (2, 2, "Probe 2 Error"),
            (2, 4, "Probe 3 Error"),
            (2, 8, "Probe 4 Error"),
            (2, 16, "Probe 5 Error"),
            (2, 32, "Probe 6 Error"),
            (2, 64, "Probe 7 Error"),
            (2, 128, "Probe 8 Error"),
            (2, 256, "Probe 9 Error"),
            (2, 512, "Probe 10 Error"),
            (2, 1024, "Probe 11 Error"),
            (2, 2048, "Module 1 Probe 1 Error"),
            (2, 4096, "Module 1 Probe 2 Error"),
            (2, 8192, "Module 1 Probe 3 Error"),
            (2, 16384, "Module 1 Probe 4 Error"),
            (2, 32768, "Module 1 Probe 5 Error"),
            (3, 1, "Module 1 Probe 6 Error"),
            (3, 2, "Module 1 Probe 7 Error"),
            (3, 4, "Module 1 Probe 8 Error"),
            (3, 8, "Module 1 Probe 9 Error"),
            (3, 16, "Module 1 Probe 10 Error"),
            (3, 32, "Module 1 Probe 11 Error"),
            (3, 64, "Module 2 Probe 1 Error"),
            (3, 128, "Module 2 Probe 2 Error"),
            (3, 256, "Module 2 Probe 3 Error"),
            (3, 512, "Module 2 Probe 4 Error"),
            (3, 1024, "Module 2 Probe 5 Error"),
            (3, 2048, "Module 2 Probe 6 Error"),
            (3, 4096, "Module 2 Probe 7 Error"),
            (3, 8192, "Module 2 Probe 8 Error"),
            (3, 16384, "Module 2 Probe 9 Error"),
            (3, 32768, "Module 2 Probe 10 Error"),
            (4, 1, "Module 2 Probe 11 Error"),
            (4, 2, "Link Inverter 1"),
            (4, 4, "Link Inverter 2"),
            (4, 8, "Link Inverter 3"),
            (4, 16, "Hardware Fault Inverter 1"),
            (4, 32, "Hardware Fault Inverter 2"),
            (4, 64, "Hardware Fault Inverter 3"),
            (4, 128, "Overcurrent Inverter 1"),
            (4, 256, "Overcurrent Inverter 2"),
            (4, 512, "Overcurrent Inverter 3"),
            (4, 1024, "High Temp Inverter 1"),
            (4, 2048, "High Temp Inverter 2"),
            (4, 4096, "High Temp Inverter 3"),
            (4, 8192, "Bad Voltage Inverter 1"),
            (4, 16384, "Bad Voltage Inverter 2"),
            (4, 32768, "Bad Voltage Inverter 3"),
            (5, 1, "Phase Sequence Inverter 1"),
            (5, 2, "Phase Sequence Inverter 2"),
            (5, 4, "Phase Sequence Inverter 3"),
            (5, 8, "Model Error Inverter 1"),
            (5, 16, "Model Error Inverter 2"),
            (5, 32, "Model Error Inverter 3"),
            (5, 64, "Overload Inverter 1"),
            (5, 128, "Overload Inverter 2"),
            (5, 256, "Overload Inverter 3"),
            (5, 512, "Overcurrent PFC Inverter 1"),
            (5, 1024, "Overcurrent PFC Inverter 2"),
            (5, 2048, "Overcurrent PFC Inverter 3"),
            (5, 4096, "Internal Comm Error Inverter 1"),
            (5, 8192, "Internal Comm Error Inverter 2"),
            (5, 16384, "Internal Comm Error Inverter 3"),
            (5, 32768, "Fault PFC Inverter 1"),
            (6, 1, "Fault PFC Inverter 2"),
            (6, 2, "Fault PFC Inverter 3"),
            (6, 4, "Probe Error Inverter 1"),
            (6, 8, "Probe Error Inverter 2"),
            (6, 16, "Probe Error Inverter 3"),
            (6, 32, "Abnormal Condition Inverter 1"),
            (6, 64, "Abnormal Condition Inverter 2"),
            (6, 128, "Abnormal Condition Inverter 3"),
            (6, 256, "EEPROM Error Inverter 1"),
            (6, 512, "EEPROM Error Inverter 2"),
            (6, 1024, "EEPROM Error Inverter 3"),
            (6, 2048, "High Discharge Temp Cp3"),
            (6, 4096, "Anti-Legionella Success"),
            (6, 8192, "Anti-Legionella Failed/Stopped"),
          ] %}
          {% set found = namespace(error="OK") %}
          {% for reg_idx, bitmask, name in alarm_map %}
            {% if regs[reg_idx] | int | bitwise_and(bitmask) %}
              {% set found.error = name %}
              {% break %}
            {% endif %}
          {% endfor %}
          {{ found.error }}
      - name: "Machine Status"
        state: >-
          {% set v = states('sensor.machine_status_raw') | int %}
          {% set map = {
            0: 'Standby',
            1: 'Cooling',
            2: 'Heating',
            4: 'Sanitary Only',
            5: 'Cooling + Sanitary',
            6: 'Heating + Sanitary'
          } %}
          {{ map.get(v, 'Unknown') }}
        icon: mdi:heat-pump

  - select:
      - name: "Machine Mode"
        state: "{{ states('sensor.machine_status') }}"
        options: "{{ ['Standby', 'Cooling', 'Heating', 'Sanitary Only', 'Cooling + Sanitary', 'Heating + Sanitary']}}"
        select_option:
          service: modbus.write_register
          data:
            hub: maxa_rtu_gateway
            unit: 1
            address: 7200
            value: >-
              {% set map = {
                'Standby': 0,
                'Cooling': 1,
                'Heating': 2,
                'Sanitary Only': 4,
                'Cooling + Sanitary': 5,
                'Heating + Sanitary': 6
              } %}
              {{ map[option] }}
        icon: mdi:heat-pump

this config adds a whole bunch of entities to the modbus integration, and also some template sensor to decode the current operating mode (Machine Status) the set mode (Machine Mode) and the active error code description if present (Current Alarm),
To use thiss you should only change the ip address of the elfin module to the actual ip in your network and paste all of this configuration in your configuration.yaml, or add into a file and then import it with something like:

homeassistant:
  packages:
    maxa: !include maxa_modbus.yaml

here’s a peek of it working





If anyone else tries this, please do let me know!, I’m also open to any suggestion for improving the config (and will probably do a follow up if i do some improvements aswell).

Here’s also some useful resources to get into this rabbit hole:

MAXA i-32 controller manual (inlcudes modbus specification)
GC-1201s manual english translated
GC-1201s configuration software NOTE: this WILL be detected as a virus. i’m pretty sure it’s a case of chronically bad chinese programming, but i took precaution when using it and you should too, use at your own risk. Because of virus detection i had to encrypt it to a 7z file for it not to get flagged. the password for the file is 1234

1 Like