Integrating Daikin using Modbus and Airzone

There are many ways to add a Daikin HVAC into HA but there are also even more varieties of Daikin/Airzone out there that may not work with integrations or prefabricated products out there. My unit happened to not work in any of the pre made integrations from HACS or HA proper so I went down the road of connecting via modbus/MQTT/RPI to my unit.
If you are looking to integrate your HVAC try the integrations already available.
DKN Cloud for HASS

Daikin AC

Go DAIKIN

Also check out these prefabricated products:
Faikin
Faikout

Additionally, I think a simple RS485 to Wi-Fi gateway device would work with the built-in HA Modbus integration with minimal effort. These can run about $60 and make this project a lot easier.
I had some spare parts on hand and old RPI (overkill) that needed a home somewhere.

My Goal was to create a simple way to integrate my Daikin HVAC into Home Assistant without cloud dependencies.

My Equipment:

  • Airzone AZWSDAI0630 (required as it is the Modbus hub but any Airzone controller should work not this specific model number)
  • Indoor ducted air handler FDMQ24WVJU9 (Airzone is mounted on this device)

Parts list:

  • RPi zero 2 (any RPi with Wi-Fi or w/adapter will work) (An ESP32 device would work as well but your code would need to be updated. Use AI to code quickly!)
  • USB to RS485 module ($16 on * amazon). Buy one with FTDI based or SP485EEN chip for best experience. Also note do not try to use uart(GPIO pins) to rs485 as they DO NOT WORK in my experience, for this project.
  • micro usb to usb A (female) wire (connect rs485 module to rpi zero 2.) ***You may not need this based on your RPi of choice but most likely will need a short USB extension cord
  • power brick and usb cable
  • Wires for connecting RS485 module to the Airwave device. Twisted pairs is preferred such as ethernet cable. I used doorbell wire laying around and twisted it with a drill.
  • One 4-pin 3.81 pitch phoenix screw terminal block connector. (on * amazon) (only needed if your HVAC techs didn’t leave the terminal block in the Airzone)
  • SD card that fits your RPi (8gb or larger should be sufficient)

The build:

Pretty simple but connect the RS485 module to an available USB on your RPI (ensure you use the data OTG usb socket rather than the PWR socket if using the RPi Zero) Connect the RS485 device to the Airzone device using two wires of your choosing. The USB device clearly indicates A+ and B- Terminals while the Airzone device provides less than desired indicators on the physical terminals. I believe all Airzone modules use the same terminals but double check your airzone modbus documentation to be sure. Otherwise connect to the terminals as shown in the photo below. The RS485 A+ terminal to the A/BMS+ (blue) terminal and RS485 Terminal B- to the B/BMS- (green) terminal. If they are reversed there will be no issues or damage done it just won’t communicate. **If you don’t get communication on the first try swap the wires.

When you are done testing make sure to print or buy an enclosure for your RPi.

Programming RPI:

Start by using Rpi Imager to flash the appropriate version of RPi OS Lite on your device. (64 or 32 bit is fine) Enable SSH, set Wi-Fi SSID and password, and all the other initial setup info then start the program writing to your SD card. When RPi Imager is done put that SD right into your RPi and fire it up. Now head back to your PC and SSH into the RPI using Putty or your preferred program.

Update your RPi software:
sudo apt update
sudo apt upgrade -y

Install, create and activate python environment:
sudo apt install python3-venv -y
python3 -m venv ~/airzone-env
source ~/airzone-env/bin/activate

Install MQTT:
pip install pyserial paho-mqtt

ID your rs485 adapter:
ls /dev/serial/by-id/
expected out put (similiar): usb-FTDI_FT232R_USB_UART_BG01PRL4-if00-port0
Copy down full path for use later example:
/dev/serial/by-id/usb-FTDI_FT232R_USB_UART_BG01PRL4-if00-port0

Create python MQTT Modbus script:
sudo nano airzone_bridge.py
paste the snippet below into your new file: update password and IP first!

Remember the password as you will use it when creating a user in MQTT in HA later.
Update the IP address to your HA instance or wherever your mosquito broker is.

#!/home/pi/airzone-env/bin/python

import serial
import time
import struct
import json
import paho.mqtt.client as mqtt

# =========================================================
# CONFIG
# =========================================================

MQTT_BROKER = "192.168.1.31"
MQTT_PORT = 1883
MQTT_USER = "airzone"
MQTT_PASS = "create_your_password"

PORT = "/dev/serial/by-id/usb-FTDI_FT232R_USB_UART_BG01PRL4-if00-port0"

SLAVE_ID = 1

DEVICE_NAME = "Daikin Mini Split"
DEVICE_ID = "daikin_mini_split"

# =========================================================
# SERIAL
# =========================================================

ser = serial.Serial(
    port=PORT,
    baudrate=19200,
    bytesize=8,
    parity=serial.PARITY_EVEN,
    stopbits=1,
    timeout=2
)

# =========================================================
# MQTT
# =========================================================

client = mqtt.Client(
    callback_api_version=mqtt.CallbackAPIVersion.VERSION2
)

client.username_pw_set(MQTT_USER, MQTT_PASS)

# =========================================================
# MODBUS CRC
# =========================================================

def modbus_crc(data):

    crc = 0xFFFF

    for pos in data:

        crc ^= pos

        for _ in range(8):

            if crc & 1:
                crc >>= 1
                crc ^= 0xA001
            else:
                crc >>= 1

    return struct.pack('<H', crc)

# =========================================================
# READ REGISTER
# =========================================================

def read_register(register):

    request = bytes([
        SLAVE_ID,
        0x03,
        (register >> 8) & 0xFF,
        register & 0xFF,
        0x00,
        0x01
    ])

    request += modbus_crc(request)

    ser.write(request)

    time.sleep(0.2)

    response = ser.read(100)

    if len(response) >= 5:

        value = response[3] << 8 | response[4]

        return value

    return None

# =========================================================
# WRITE REGISTER
# =========================================================

def write_register(register, value):

    request = bytes([
        SLAVE_ID,
        0x06,
        (register >> 8) & 0xFF,
        register & 0xFF,
        (value >> 8) & 0xFF,
        value & 0xFF
    ])

    request += modbus_crc(request)

    print(f"Writing register {register} value {value}")

    ser.write(request)

    time.sleep(0.2)

    response = ser.read(100)

    if response:

        print(f"Write response: {response.hex()}")

        return True

    print("No write response")

    return False

# =========================================================
# MQTT DISCOVERY
# =========================================================

def publish_discovery():

    discovery_topic = (
        f"homeassistant/climate/{DEVICE_ID}/config"
    )

    payload = {

        "name": DEVICE_NAME,
        "unique_id": DEVICE_ID,

        # -------------------------------------------------
        # HVAC MODES
        # -------------------------------------------------

        "mode_state_topic":
            "airzone/mode/state",

        "mode_command_topic":
            "airzone/mode/set",

        "modes": [
            "off",
            "auto",
            "cool",
            "heat",
            "fan_only",
            "dry"
        ],

        # -------------------------------------------------
        # TEMPERATURES
        # -------------------------------------------------

        "temperature_state_topic":
            "airzone/setpoint/state",

        "temperature_command_topic":
            "airzone/setpoint/set",

        "current_temperature_topic":
            "airzone/roomtemp/state",

        "temp_step": 1,
        "min_temp": 60,
        "max_temp": 86,

        "precision": 1.0,

        # -------------------------------------------------
        # FAN MODES
        # -------------------------------------------------

        "fan_mode_state_topic":
            "airzone/fan/state",

        "fan_mode_command_topic":
            "airzone/fan/set",

        "fan_modes": [
            "auto",
            "1",
            "2",
            "3"
        ],

        # -------------------------------------------------
        # DEVICE INFO
        # -------------------------------------------------

        "device": {
            "identifiers": [DEVICE_ID],
            "name": DEVICE_NAME,
            "manufacturer": "Daikin/Airzone",
            "model": "Airzone Aidoo",
            "sw_version": "1.0"
        }
    }

    client.publish(
        discovery_topic,
        json.dumps(payload),
        retain=True
    )

    print("Published MQTT discovery")

# =========================================================
# MQTT CALLBACK
# =========================================================

def on_message(client, userdata, msg):

    topic = msg.topic
    payload = msg.payload.decode()

    print(f"MQTT command received:")
    print(f"Topic: {topic}")
    print(f"Payload: {payload}")

    try:

        # -------------------------------------------------
        # SETPOINT
        # -------------------------------------------------

        if topic == "airzone/setpoint/set":

            temp = int(float(payload) * 10)

            success = write_register(1, temp)

            print(f"Setpoint write success: {success}")

        # -------------------------------------------------
        # HVAC MODES
        # -------------------------------------------------

        elif topic == "airzone/mode/set":

            # OFF
            if payload == "off":

                success = write_register(0, 0)

                print(f"Power OFF success: {success}")

            else:

                # Ensure power ON
                write_register(0, 1)

                modes = {
                    "auto": 1,
                    "cool": 2,
                    "heat": 3,
                    "fan_only": 4,
                    "dry": 5
                }

                if payload in modes:

                    success = write_register(
                        3,
                        modes[payload]
                    )

                    print(f"Mode write success: {success}")

        # -------------------------------------------------
        # FAN SPEED
        # -------------------------------------------------

        elif topic == "airzone/fan/set":

            fan_modes = {
                "auto": 0,
                "1": 1,
                "2": 2,
                "3": 3
            }

            if payload in fan_modes:

                success = write_register(
                    54,
                    fan_modes[payload]
                )

                print(f"Fan write success: {success}")

    except Exception as e:

        print(f"MQTT write error: {e}")

# =========================================================
# MQTT CONNECT
# =========================================================

print("Connecting to MQTT broker...")

client.on_message = on_message

client.connect(
    MQTT_BROKER,
    MQTT_PORT,
    60
)

print("Connected to MQTT broker")

client.subscribe("airzone/setpoint/set")
client.subscribe("airzone/mode/set")
client.subscribe("airzone/fan/set")

print("MQTT subscriptions active")

client.loop_start()

publish_discovery()

# =========================================================
# MAIN LOOP
# =========================================================

while True:

    try:

        power = read_register(0)
        setpoint = read_register(1)
        room_temp = read_register(2)
        mode = read_register(3)
        fan = read_register(54)

        # -------------------------------------------------
        # SETPOINT
        # -------------------------------------------------

        if setpoint is not None:

            client.publish(
                "airzone/setpoint/state",
                setpoint / 10,
                retain=True
            )

        # -------------------------------------------------
        # ROOM TEMP
        # -------------------------------------------------

        if room_temp is not None:

            client.publish(
                "airzone/roomtemp/state",
                room_temp / 10,
                retain=True
            )

        # -------------------------------------------------
        # POWER
        # -------------------------------------------------

        if power is not None:

            client.publish(
                "airzone/power/state",
                power,
                retain=True
            )

        # -------------------------------------------------
        # HVAC MODE
        # -------------------------------------------------

        if power == 0:

            client.publish(
                "airzone/mode/state",
                "off",
                retain=True
            )

        elif mode is not None:

            mode_map = {
                1: "auto",
                2: "cool",
                3: "heat",
                4: "fan_only",
                5: "dry"
            }

            client.publish(
                "airzone/mode/state",
                mode_map.get(mode, "unknown"),
                retain=True
            )

        # -------------------------------------------------
        # FAN MODE
        # -------------------------------------------------

        if fan is not None:

            fan_map = {
                0: "auto",
                1: "1",
                2: "2",
                3: "3"
            }

            client.publish(
                "airzone/fan/state",
                fan_map.get(fan, "auto"),
                retain=True
            )

        # -------------------------------------------------
        # STATUS OUTPUT
        # -------------------------------------------------

        room_display = "?"
        setpoint_display = "?"
        mode_display = "?"
        fan_display = "?"

        if room_temp is not None:
            room_display = room_temp / 10

        if setpoint is not None:
            setpoint_display = setpoint / 10

        if mode is not None:
            mode_display = mode

        if fan is not None:
            fan_display = fan

        print(
            f"Room: {room_display}F | "
            f"Setpoint: {setpoint_display}F | "
            f"Mode: {mode_display} | "
            f"Fan: {fan_display}"
        )

        time.sleep(5)

    except Exception as e:

        print(f"Main loop error: {e}")

        time.sleep(5)

Make script executable:

chmod +x ~/airzone_bridge.py

Create systemd service:

sudo nano airzone-bridge.service

Then paste the below into the new file

[Unit]
Description=Airzone MQTT Modbus Bridge
After=network.target

[Service]
ExecStart=/home/pi/airzone-env/bin/python /home/pi/airzone_bridge.py
WorkingDirectory=/home/pi
StandardOutput=inherit
StandardError=inherit
Restart=always
User=pi

[Install]
WantedBy=multi-user.target

Enable autostart:

sudo systemctl daemon-reload
sudo systemctl enable airzone-bridge
sudo systemctl start airzone-bridge

Log out of your RPi, pat your self on the back and open Home Assistant.

Home assistant Config:

Install MQTT broker, in the apps section of HA, if you don’t already have it. Link here. Then go into the configuration and create a new username and password. This is from your python script above. Username: airzone password: create_your_own. Once complete make sure you save out of the dialog box AND save out of the config section!

Install the MQTT integration if you don’t already have it. Open the integration and it should auto discover your new Daikin mini split system. May have to reload or restart HA to get the auto discovery to see the data.

Create a cool lovelace card:

Install Mushroom card from HACS click here

Then open your dashboard and create new card using the manual card option (select manual when adding new card) paste the yaml below into your new manual card;

type: vertical-stack
cards:
  - type: custom:mushroom-title-card
    title: Daikin Mini Split
    subtitle: Airzone Modbus HVAC
  - type: thermostat
    entity: climate.daikin_mini_split
    features:
      - type: climate-hvac-modes
        hvac_modes:
          - "off"
          - auto
          - cool
          - heat
          - fan_only
          - dry
  - type: horizontal-stack
    cards:
      - type: button
        entity: climate.daikin_mini_split
        name: "OFF"
        tap_action:
          action: call-service
          service: climate.set_hvac_mode
          target:
            entity_id: climate.daikin_mini_split
          data:
            hvac_mode: "off"
      - type: button
        entity: climate.daikin_mini_split
        name: AUTO
        tap_action:
          action: call-service
          service: climate.set_hvac_mode
          target:
            entity_id: climate.daikin_mini_split
          data:
            hvac_mode: auto
      - type: button
        entity: climate.daikin_mini_split
        name: COOL
        tap_action:
          action: call-service
          service: climate.set_hvac_mode
          target:
            entity_id: climate.daikin_mini_split
          data:
            hvac_mode: cool
      - type: button
        entity: climate.daikin_mini_split
        name: HEAT
        tap_action:
          action: call-service
          service: climate.set_hvac_mode
          target:
            entity_id: climate.daikin_mini_split
          data:
            hvac_mode: heat
  - type: horizontal-stack
    cards:
      - type: button
        name: FAN
        tap_action:
          action: call-service
          service: climate.set_hvac_mode
          target:
            entity_id: climate.daikin_mini_split
          data:
            hvac_mode: fan_only
      - type: button
        name: DRY
        tap_action:
          action: call-service
          service: climate.set_hvac_mode
          target:
            entity_id: climate.daikin_mini_split
          data:
            hvac_mode: dry
  - type: entities
    title: Fan Speed
    entities:
      - entity: climate.daikin_mini_split
        name: Fan Mode
  - type: custom:mini-climate
    entity: climate.daikin_mini_split
    name: Daikin HVAC
    icon: mdi:air-conditioner

That should just about do it for this guide. Let me know if i skipped on any details you find useful.

So the Raspberry Pi is basically a RS485/ModBus to MQTT converter?
If I use a ESP32 RS485 to MQTT device instead to get the data into MQTT, will all your other associated code and dashboard panels still work?

Correct the RPi is just the rs485 to MQTT converter. The rest should work as long as the names are retained.

I originally documented a workaround for integrating my Daikin mini-split through the Airzone DKN/Aidoo Modbus interface using a Raspberry Pi, USB RS-485 adapter, Python, and MQTT.

That solution worked, but it required an additional Raspberry Pi, a USB RS-485 interface, a Python bridge, MQTT, and several pieces of configuration to keep everything synchronized.

I’ve now replaced that entire setup with a Home Assistant HACS integration that communicates directly with the Airzone Modbus interface through an Ethernet-to-RS485 gateway.

New architecture

The current setup is:

Daikin indoor unit → Airzone DKN/Aidoo → RS-485 → Elfin EW11 → Ethernet/Modbus TCP → Home Assistant

There is no Raspberry Pi, Python bridge, or MQTT involved.

The EW11 is configured as a Modbus TCP server and simply provides the RS-485 connection to Home Assistant.

The HACS integration

I’ve created a custom Home Assistant integration:

Daikin Modbus HACS integration

It uses Home Assistant’s newer Modbus integration architecture introduced in Core 2026.9. Instead of requiring users to manually configure a Modbus YAML hub, the integration has its own config flow and asks Home Assistant’s native Modbus integration for the appropriate Modbus unit. Home Assistant specifically introduced async_get_unit for this type of integration-based Modbus device setup.

Setup

After installing the integration through HACS, the device is configured through Home Assistant’s normal integration setup:

  • Host: IP address/hostname of the Modbus TCP gateway
  • Port: Modbus TCP port, normally 502 (may have to change in your EW11 hardware by going to the gateway IP address)
  • Unit ID: Airzone Modbus slave address, normally 1

The integration validates the connection during setup, so there is no separate YAML Modbus configuration required.

For my installation:

Host: 192.168.1.29
Port: 502
Unit ID: 1

What it provides

The integration creates a Daikin HVAC climate entity and associated sensors.

The current implementation provides:

  • Power on/off
  • Setpoint
  • Room temperature
  • HVAC mode
    • Auto
    • Cooling
    • Heating
    • Fan
    • Dry
  • Fan control
  • Fan percentage
  • Louver state
  • Available modes
  • Available fan speeds

The resulting climate entity in my installation is:

climate.daikin_hvac

Airzone register mapping

The integration is based on the documented Airzone Modbus register map rather than relying on the old MQTT bridge.

Some of the important registers are:

Register Function
0 Power
1 Setpoint
2 Room temperature
3 HVAC mode
4 Fan percentage
5 Louvers
14 Available modes
15 Available speeds
54 Numeric fan speed

One interesting detail with my particular installation is that the Airzone interface reports temperatures in tenths of a degree Fahrenheit, despite the documentation describing the temperature registers in Celsius. For example, a register value of 730 corresponds to 73.0°F.

Why I moved away from the Raspberry Pi/MQTT solution

The original solution was essentially:

Daikin
↓
Airzone DKN/Aidoo
↓
RS-485
↓
Raspberry Pi
↓
Python Modbus bridge
↓
MQTT
↓
Home Assistant

The new setup is:

Daikin
↓
Airzone DKN/Aidoo
↓
RS-485
↓
EW11
↓
Modbus TCP
↓
Home Assistant

This removes an entire computer and software layer from the system.

There is also no need for MQTT discovery, an MQTT broker, a Python daemon, systemd service, or a custom serial bridge.

One important HA version requirement

This integration is built around the newer Modbus architecture in Home Assistant 2026.9+. Home Assistant’s Modbus modernization allows device integrations to collect their own connection information and obtain a shared Modbus unit from the native Modbus integration.

That is particularly useful for this application because the integration itself knows the Airzone register map, while Home Assistant handles the underlying Modbus TCP connection.

Standalone Modbus library

The device/register implementation is also maintained separately:

Daikin Modbus Python library

The HACS integration vendors the device library so users don’t have to separately install a Python package.

Current status

I’ve now tested the complete path on my actual installation:
Home Assistant 2026.9.3
Airzone DKN/Aidoo
Elfin EW11
Modbus TCP
Unit ID 1
Native Home Assistant Modbus
HACS Daikin Modbus integration

Raw Modbus reads from Home Assistant successfully reach the Airzone interface and return the expected register values, and the resulting climate.daikin_hvac entity is now working.

So the Raspberry Pi/MQTT workaround documented in my original post is no longer necessary for this setup.

Repository: Daikin Modbus HACS

Python device library: Daikin Modbus

Elfin EW11 Setup

The Elfin EW11 provides the Ethernet-to-RS485 bridge between the Airzone DKN/Aidoo and Home Assistant.

1. Connect the RS485 wiring

Connect the Airzone RS485 terminals to the EW11:

Airzone EW11
A / BMS+ A / D+
B / BMS- B / D-

The connection is RS485 half-duplex.

2. Configure the EW11 serial settings

In the EW11 web interface, configure the UART/serial port as:

  • Baud rate: 19200
  • Data bits: 8
  • Stop bits: 1
  • Parity: Even
  • Flow control: None
  • Mode: RS485 Half Duplex

These settings match the default communication parameters of the Airzone Modbus interface.

3. Configure the EW11 network settings

Configure the EW11 as a Modbus TCP Server.

  • Protocol: Modbus
  • TCP Mode: Server
  • TCP Port: 502
  • TCP Buffer: 512
  • Keepalive: 60
  • Timeout: 0
  • Maximum connections: 3
  • Security: Disabled
  • Route: UART

Give the EW11 a static IP address or DHCP reservation.

Example:

  • EW11 IP: 192.168.1.29
  • Modbus TCP port: 502

4. Airzone Modbus settings

The Airzone DKN/Aidoo remains the Modbus slave.

Default settings:

  • Slave address: 1
  • Baud rate: 19200
  • Parity: Even
  • Data bits: 8
  • Stop bits: 1

No changes to the Airzone configuration are required if it is still using its defaults.