Control Daikin Madoka BRC1H Thermostat via Bluetooth — HA Custom Integration + ESPHome Component

Introduction

Hey everyone!

Sharing my custom integration and ESPHome component for controlling Daikin Madoka BRC1H thermostats from Home Assistant via Bluetooth.

The Madoka BRC1H is a sleek wall-mounted thermostat by Daikin, controllable only via the Daikin mobile app (Bluetooth). No Wi-Fi, no usable cloud API. This project brings full control directly into HA.

Two approaches available:

  1. HA Custom Integration — direct Bluetooth connection from HA server
  2. ESPHome Component — ESP32 acts as a Bluetooth proxy (place it anywhere)

GitHub: GitHub - dasimon135/daikin_madoka: Home Assistant custom component integration for the BRC1H thermostat (madoka) (branch madoka)


Features

  • Full HVAC control: mode (heat, cool, auto, fan, dry), target temperature, fan speed
  • Indoor temperature sensor reading
  • Fan modes: Auto, Low, Mid, High
  • Temperature range: 16°C - 32°C (61°F - 90°F)
  • Automatic BLE reconnection
  • Compatible with ESP32 and ESP32-S3 (M5Stack Atom Lite / Atom S3 Lite)

Option 1: HA Custom Integration (Direct Bluetooth)

Requirements

  • Home Assistant with Bluetooth access (USB BLE adapter if needed)
  • If HA runs in Docker: DBUS access required (see below)
  • Thermostat must be within Bluetooth range (~10m / ~30ft)

Installation

  1. Download from GitHub
  2. Copy to custom_components/daikin_madoka/ in your HA config
  3. Restart Home Assistant

Bluetooth Pairing (mandatory)

This is the critical step. The BRC1H requires secure pairing:

# 1. Disconnect the thermostat from any other device (Madoka BT menu → Forget)

# 2. On the HA server (or the machine with the BT adapter):
bluetoothctl
agent KeyboardDisplay
remove <BRC1H_MAC>          # Remove any previous pairing
scan on                      # Wait for BRC1H to appear
scan off
pair <BRC1H_MAC>            # Accept the prompt + confirm on thermostat

:warning: Confirm quickly on the thermostat after pair, otherwise the pairing times out.

HA Configuration

Go to Settings → Integrations → Add → Daikin Madoka and provide:

  • Bluetooth MAC address of the BRC1H
  • Bluetooth adapter name (usually hci0)

Two entities are created:

  • Climate: full control (mode, temperature, fan)
  • Sensor: measured indoor temperature

Docker / VM: DBUS Configuration

If HA runs in Docker, DBUS must be accessible:

# docker-compose.yml
volumes:
  - /var/run/dbus/system_bus_socket:/var/run/dbus/system_bus_socket
privileged: true

To verify it works:

docker exec -ti <container_id> /bin/bash
bleak-lescan -i hci0

Option 2: ESP32 Proxy via ESPHome (recommended)

This is what I use daily. An M5Stack Atom Lite (or any ESP32) placed near the thermostat acts as a Bluetooth proxy.

Why this approach?

  • No need for the HA server to be within Bluetooth range
  • Works flawlessly in Docker/VM without DBUS hassle
  • Single ESP32 can handle multiple thermostats
  • Excellent reliability with automatic reconnection

Tested Hardware

Platform Chip Framework Status
M5Stack Atom Lite ESP32 ESP-IDF :white_check_mark:
M5Stack Atom S3 Lite ESP32-S3 ESP-IDF 5.x :white_check_mark:
Generic ESP32 DevKit ESP32 ESP-IDF / Arduino :white_check_mark:

Component Installation

external_components:
  # From GitHub directly:
  - source: github://dasimon135/daikin_madoka@madoka
    components: [ madoka, ble_client ]

  # OR locally (copy esphome_components/ to your ESPHome config):
  # - source:
  #     type: local
  #     path: esphome_components
  #   components: [ madoka, ble_client ]

The included ble_client is a patched version compatible with ESPHome 2025.10.0+ (fixes consume_connection_slots removal).

Full Config (M5Stack Atom Lite)

substitutions:
  name: madoka-proxy
  friendly_name: "Madoka BLE Proxy"

esphome:
  name: ${name}
  friendly_name: ${friendly_name}

esp32:
  board: m5stack-atom
  framework:
    type: esp-idf

wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_password

logger:
  level: DEBUG

api:
  encryption:
    key: !secret api_key

ota:

external_components:
  - source: github://dasimon135/daikin_madoka@madoka
    components: [ madoka, ble_client ]

esp32_ble_tracker:
  max_connections: 2

# IMPORTANT: disable classic BT proxy
bluetooth_proxy:
  active: false

ble_client:
  - mac_address: "F0:B3:1E:87:AF:FE"  # ← Your Madoka MAC
    id: madoka_living
    on_disconnect:
      then:
        - ble_client.connect: madoka_living

climate:
  - platform: madoka
    name: "Madoka Living Room"
    ble_client_id: madoka_living
    update_interval: 15s

ESP32-S3 Config (M5Stack Atom S3 Lite)

For ESP32-S3, add the BLE security configuration:

esp32:
  board: m5stack-atoms3
  variant: esp32s3
  framework:
    type: esp-idf
    version: recommended
    sdkconfig_options:
      CONFIG_BT_BLE_50_FEATURES_SUPPORTED: y
      CONFIG_BT_BLE_42_FEATURES_SUPPORTED: y

# REQUIRED for Madoka pairing on ESP32-S3
esp32_ble:
  io_capability: display_yes_no

Pairing Process (ESP32)

  1. Flash the config and open ESPHome logs
  2. The ESP32 connects to the Madoka and initiates pairing
  3. A 6-digit code appears in the logs:
╔══════════════════════════════════════════════════════════╗
║  PAIRING CODE: 790440                                   ║
║  Check that this code matches the one on the Madoka     ║
║  and CONFIRM on the thermostat!                         ║
╚══════════════════════════════════════════════════════════╝
  1. The same code appears on the thermostat screen
  2. Confirm on the thermostat (press OK) — the ESP32 auto-confirms on its side
  3. Subsequent connections are automatic (bonding is saved)

Multiple Thermostats

A single ESP32 can handle 2 thermostats simultaneously (max_connections: 2). Add a second ble_client + climate:

ble_client:
  - mac_address: "F0:B3:1E:87:AF:FE"
    id: madoka_living
    on_disconnect:
      then:
        - ble_client.connect: madoka_living
  - mac_address: "1C:54:9E:90:E3:0E"
    id: madoka_bedroom
    on_disconnect:
      then:
        - ble_client.connect: madoka_bedroom

climate:
  - platform: madoka
    name: "Madoka Living Room"
    ble_client_id: madoka_living
    update_interval: 15s
  - platform: madoka
    name: "Madoka Bedroom"
    ble_client_id: madoka_bedroom
    update_interval: 15s

Troubleshooting

Issue Solution
“device not found” in HA integration Thermostat is connected to another device (mobile app). Forget it first.
“cannot connect” DBUS not available (Docker) or BT adapter missing
Pairing fails (error 0x52) On ESP32-S3: ensure esp32_ble: io_capability: display_yes_no is set
2nd thermostat won’t connect Normal — they connect one at a time. Wait for the 1st to pair.
AttributeError: consume_connection_slots Use the patched ble_client included in this repo

Credits

  • Original HA integration: @mduran80 / pymadoka
  • ESPHome madoka component: Petapton/esphome
  • ESPHome 2025.10+ compatibility fixes & ESP32-S3 support: this repo

GitHub: GitHub - dasimon135/daikin_madoka: Home Assistant custom component integration for the BRC1H thermostat (madoka) (branch madoka)

If you have Madoka thermostats at home, give it a try and let me know how it goes! :slightly_smiling_face:

I actually used some shelly gen 3’s to achieve the same (and still use them as light switch too).

All I had to do was re-flash them with esphome :innocent:

Thanks for the integration; this is exactly what I’m looking for. My HA machine is within 2 meters from the Daikin Madoka, so it should be possible to use the BT adapter on my HA machine to connect with the Madoka. Some ‘noob’ questions:

  • How can I upload the integration to the custom_components/daikin_madoka/? I have downloaded the ZIP file from Github, but how to install it on my HA machine? I have installed integrations via HACS, but never manually.
  • Which add-on is used for the BT pairing process?

Thanks in advance.

Just uploaded to ZIP file to the HA server, unpacked it via Terminal & SSH in custom_components. Also succesfully paired my Madoka via bluetoothctl. But when I want to add the integration via Settings → Integrations → Add → Daikin Madoka, I got this failure:

What I am doing wrong…? Information from log:

Logger: homeassistant.config_entries
Source: config_entries.py:3969
First occurred: 2:43:35 PM (4 occurrences)
Last logged: 3:51:19 PM

Error occurred loading flow for integration daikin_madoka: cannot import name ‘discover’ from ‘bleak’ (/usr/local/lib/python3.13/site-packages/bleak/init.py)

Bei mir kommt der gleiche Fehler: Der Konfigurationsfluss konnte nicht geladen werden: {“message”:“Invalid handler specified”}

Logger: homeassistant.config_entries
Quelle: config_entries.py:3969
Erstmals aufgetreten: 06:01:51 (1 Vorkommnis)
Zuletzt protokolliert: 06:01:51

Error occurred loading flow for integration daikin_madoka: cannot import name ‘discover’ from ‘bleak’ (/usr/local/lib/python3.13/site-packages/bleak/init.py)

Logger: homeassistant.util.loop
Quelle: util/loop.py:137
Erstmals aufgetreten: 06:01:51 (1 Vorkommnis)
Zuletzt protokolliert: 06:01:51
Detected blocking call to import_module with args (‘custom_components.daikin_madoka.config_flow’,) inside the event loop by integration ‘config’ at homeassistant/components/config/config_entries.py, line 195: return await super()._post_impl(request, data) (offender: /usr/src/homeassistant/homeassistant/loader.py, line 1307: return importlib.import_module(f"{self.pkg_path}.{platform_name}")), please create a bug report at GitHub · Where software is built For developers, please see Blocking operations with asyncio | Home Assistant Developer Docs Traceback (most recent call last): File “”, line 198, in _run_module_as_main File “”, line 88, in _run_code File “/usr/src/homeassistant/homeassistant/main.py”, line 229, in sys.exit(main()) File “/usr/src/homeassistant/homeassistant/main.py”, line 215, in main exit_code = runner.run(runtime_conf) File “/usr/src/homeassistant/homeassistant/runner.py”, line 289, in run return loop.run_until_complete(setup_and_run_hass(runtime_config)) File “/usr/local/lib/python3.13/asyncio/base_events.py”, line 712, in run_until_complete self.run_forever() File “/usr/local/lib/python3.13/asyncio/base_events.py”, line 683, in run_forever self._run_once() File “/usr/local/lib/python3.13/asyncio/base_events.py”, line 2050, in _run_once handle._run() File “/usr/local/lib/python3.13/asyncio/events.py”, line 89, in _run self._context.run(self._callback, *self._args) File “/usr/local/lib/python3.13/site-packages/aiohttp/web_protocol.py”, line 510, in _handle_request resp = await request_handler(request) File “/usr/local/lib/python3.13/site-packages/aiohttp/web_app.py”, line 569, in _handle return await handler(request) File “/usr/local/lib/python3.13/site-packages/aiohttp/web_middlewares.py”, line 117, in impl return await handler(request) File “/usr/src/homeassistant/homeassistant/components/http/security_filter.py”, line 92, in security_filter_middleware return await handler(request) File “/usr/src/homeassistant/homeassistant/components/http/forwarded.py”, line 87, in forwarded_middleware return await handler(request) File “/usr/src/homeassistant/homeassistant/components/http/request_context.py”, line 26, in request_context_middleware return await handler(request) File “/usr/src/homeassistant/homeassistant/components/http/ban.py”, line 86, in ban_middleware return await handler(request) File “/usr/src/homeassistant/homeassistant/components/http/auth.py”, line 242, in auth_middleware return await handler(request) File “/usr/src/homeassistant/homeassistant/components/http/headers.py”, line 41, in headers_middleware response = await handler(request) File “/usr/src/homeassistant/homeassistant/helpers/http.py”, line 73, in handle result = await handler(request, **request.match_info) File “/usr/src/homeassistant/homeassistant/components/http/decorators.py”, line 83, in with_admin return await func(self, request, *args, **kwargs) File “/usr/src/homeassistant/homeassistant/components/http/data_validator.py”, line 74, in wrapper return await method(view, request, data, *args, **kwargs) File “/usr/src/homeassistant/homeassistant/components/config/config_entries.py”, line 188, in post return await self._post_impl(request, data) File “/usr/src/homeassistant/homeassistant/components/config/config_entries.py”, line 195, in _post_impl return await super()._post_impl(request, data)