[Tuto] Contrôler un thermostat Daikin Madoka BRC1H via Bluetooth avec Home Assistant (intégration custom + ESPHome)

Introduction

Salut à tous !

Je partage ici mon intégration custom pour contrôler les thermostats Daikin Madoka BRC1H depuis Home Assistant via Bluetooth.

Les Madoka BRC1H sont des thermostats muraux design de Daikin, contrôlables uniquement via l’app mobile Daikin (Bluetooth). Pas de Wi-Fi, pas de cloud officiel exploitable. Cette intégration permet de les piloter directement depuis HA.

Deux approches possibles :

  1. Intégration custom HA — connexion Bluetooth directe depuis le serveur HA
  2. Composant ESPHome — un ESP32 sert de proxy Bluetooth (placement libre)

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


Fonctionnalités

  • Contrôle complet : mode (chaud, froid, auto, ventilation, déshumidification), température de consigne, vitesse ventilateur
  • Lecture de la température ambiante (sensor dédié)
  • Modes ventilateur : Auto, Low, Mid, High
  • Plage de température : 16°C - 32°C
  • Reconnexion automatique en cas de déconnexion BLE
  • Compatible ESP32 et ESP32-S3 (M5Stack Atom Lite / Atom S3 Lite)

Option 1 : Intégration Custom HA (Bluetooth direct)

Prérequis

  • Home Assistant avec accès Bluetooth (adaptateur USB BLE si besoin)
  • Si HA tourne en Docker : accès DBUS obligatoire (voir plus bas)
  • Le thermostat doit être à portée Bluetooth du serveur HA (~10m)

Installation

  1. Téléchargez le dossier depuis le repo GitHub
  2. Copiez-le dans custom_components/daikin_madoka/ de votre config HA
  3. Redémarrez Home Assistant

Appairage Bluetooth (obligatoire)

C’est l’étape critique. Le BRC1H nécessite un pairing sécurisé :

# 1. Déconnectez le thermostat de tout autre appareil (menu Bluetooth du Madoka → Oublier)

# 2. Sur le serveur HA (ou la machine avec l'adaptateur BT) :
bluetoothctl
agent KeyboardDisplay
remove <MAC_DU_BRC1H>     # Supprimer un ancien appairage éventuel
scan on                     # Attendre que le BRC1H apparaisse
scan off
pair <MAC_DU_BRC1H>        # Accepter le prompt + confirmer sur le thermostat

:warning: Confirmez rapidement sur le thermostat après pair, sinon le pairing expire.

Configuration dans HA

Allez dans Paramètres → Intégrations → Ajouter → Daikin Madoka et renseignez :

  • Adresse MAC Bluetooth du BRC1H
  • Nom de l’adaptateur Bluetooth (généralement hci0)

Deux entités sont créées :

  • Climate : contrôle complet (mode, température, ventilateur)
  • Sensor : température ambiante mesurée

Docker / VM : configuration DBUS

Si HA tourne dans Docker, DBUS doit être accessible :

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

Pour vérifier que ça fonctionne :

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

Option 2 : Proxy ESP32 via ESPHome (recommandé)

C’est l’approche que j’utilise au quotidien. Un M5Stack Atom Lite (ou tout ESP32) placé à proximité du thermostat sert de proxy Bluetooth.

Avantages

  • Pas besoin que le serveur HA soit à portée Bluetooth
  • Fonctionne parfaitement en Docker/VM sans DBUS
  • Un seul ESP32 peut gérer plusieurs thermostats
  • Fiabilité excellente avec reconnexion automatique

Matériel testé

Plateforme Chip Framework Statut
M5Stack Atom Lite ESP32 ESP-IDF :white_check_mark:
M5Stack Atom S3 Lite ESP32-S3 ESP-IDF 5.x :white_check_mark:
ESP32 DevKit générique ESP32 ESP-IDF / Arduino :white_check_mark:

Installation des composants

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

  # OU en local (copiez esphome_components/ dans votre config ESPHome) :
  # - source:
  #     type: local
  #     path: esphome_components
  #   components: [ madoka, ble_client ]

Le composant ble_client inclus est une version corrigée compatible ESPHome 2025.10.0+ (fix du consume_connection_slots supprimé).

Config complète (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 : désactiver le proxy BT classique
bluetooth_proxy:
  active: false

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

climate:
  - platform: madoka
    name: "Madoka Salon"
    ble_client_id: madoka_salon
    update_interval: 15s

Config ESP32-S3 (M5Stack Atom S3 Lite)

Pour les ESP32-S3, ajoutez la configuration de sécurité BLE :

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

# OBLIGATOIRE pour le pairing Madoka sur ESP32-S3
esp32_ble:
  io_capability: display_yes_no

Processus de pairing (ESP32)

  1. Flashez la config et ouvrez les logs ESPHome
  2. L’ESP32 se connecte au Madoka et lance le pairing
  3. Un code à 6 chiffres s’affiche dans les logs :
╔══════════════════════════════════════════════════════════╗
║  PAIRING CODE: 790440                                   ║
║  Vérifiez que ce code correspond à celui sur le Madoka  ║
║  et CONFIRMEZ sur le thermostat!                        ║
╚══════════════════════════════════════════════════════════╝
  1. Le même code s’affiche sur l’écran du thermostat
  2. Confirmez sur le thermostat (bouton OK) — l’ESP32 confirme automatiquement
  3. Les connexions suivantes sont automatiques (bonding enregistré)

Plusieurs thermostats

Un seul ESP32 peut gérer 2 thermostats simultanément (max_connections: 2). Ajoutez un deuxième ble_client + climate :

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

climate:
  - platform: madoka
    name: "Madoka Salon"
    ble_client_id: madoka_salon
    update_interval: 15s
  - platform: madoka
    name: "Madoka Chambre"
    ble_client_id: madoka_chambre
    update_interval: 15s

Dépannage

Problème Solution
« device not found » dans l’intégration HA Le thermostat est connecté à un autre appareil (app mobile). Oubliez-le d’abord.
« cannot connect » DBUS non disponible (Docker) ou adaptateur BT absent
Pairing échoue (erreur 0x52) Sur ESP32-S3 : vérifiez esp32_ble: io_capability: display_yes_no
2ème thermostat ne se connecte pas Normal, ils se connectent un par un. Attendez le pairing du 1er.
AttributeError: consume_connection_slots Utilisez le ble_client corrigé inclus dans le repo

Crédits

  • Intégration HA originale : @mduran80 / pymadoka
  • Composant ESPHome madoka : Petapton/esphome
  • Correctifs de compatibilité ESPHome 2025.10+ et support ESP32-S3 : ce repo

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

Si ça vous intéresse ou si vous avez des Madoka chez vous, n’hésitez pas à tester et à faire des retours ! :slightly_smiling_face:

Bonjour, cela m’intéresse : j’ai tenté de modifier un proxy bluetooth pour contrôler le module Daikin . c’est un Xiao w550 ethernet. mais cela plante à l’installation . :

 # Only boards produced after November 1, 2025 are supported

# ==== AUTO-SYNC START: xiao-w5500-ethernet-adapter/xiao-w5500-ethernet-adapter.yaml ====




# Only boards produced after November 1, 2025 are supported

esphome:

name: seeed-esp32-poe

friendly_name: "XIAO W5500 Ethernet Adapter V1.2"

min_version: 2025.11.0

name_add_mac_suffix: true




esp32:

variant: esp32s3

framework:

type: esp-idf

version: recommended

sdkconfig_options:

CONFIG_BT_BLE_50_FEATURES_SUPPORTED: y

CONFIG_BT_BLE_42_FEATURES_SUPPORTED: y




ethernet:

type: W5500

clk_pin: GPIO7

mosi_pin: GPIO9

miso_pin: GPIO8

cs_pin: GPIO2

interrupt_pin: GPIO10

manual_ip:

static_ip: 192.168.....   # une IP libre sur ton réseau

gateway: 192.168....

subnet: 255.255.255.0




api:

logger:




ota:

  - platform: esphome

id: ota_esphome





esp32_ble_tracker:




#bluetooth_proxy:

#  active: true

#  connection_slots: 4

# IMPORTANT : désactiver le proxy BT classique

bluetooth_proxy:

active: false




ble_client:

  - mac_address: "................"  # ← Votre MAC Madoka

id: madoka_salon

on_disconnect:

then:

        - ble_client.connect: madoka_salon




climate:

  - platform: madoka

name: "Madoka Salon"

ble_client_id: madoka_salon

update_interval: 15s






# OBLIGATOIRE pour le pairing Madoka sur ESP32-S3

esp32_ble:

max_connections: 4

io_capability: display_yes_no

button:

  - platform: safe_mode

id: button_safe_mode

name: Safe Mode Boot




  - platform: factory_reset

id: factory_reset_btn

name: Factory reset




external_components:

  - source:

type: local

path: esphome_components

components: [ madoka, ble_client ]

# ==== AUTO-SYNC END ====

le fichier logiciel :

INFO ESPHome 2026.2.1
INFO Reading configuration /config/esphome/bluetooth-proxy.yaml…
ERROR Unexpected exception while reading configuration:
Traceback (most recent call last):
File « /usr/local/bin/esphome », line 10, in
sys.exit(main())
^^^^^^
File « /esphome/esphome/main.py », line 1652, in main
return run_esphome(sys.argv)
^^^^^^^^^^^^^^^^^^^^^
File « /esphome/esphome/main.py », line 1631, in run_esphome
config = read_config(
^^^^^^^^^^^^
File « /esphome/esphome/config.py », line 1323, in read_config
res = load_config(command_line_substitutions, skip_external_update)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File « /esphome/esphome/config.py », line 1180, in load_config
return _load_config(command_line_substitutions, skip_external_update)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File « /esphome/esphome/config.py », line 1168, in _load_config
return validate_config(config, command_line_substitutions, skip_external_update)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File « /esphome/esphome/config.py », line 1059, in validate_config
target_platform = core_config.preload_core_config(config, result)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File « /esphome/esphome/core/config.py », line 358, in preload_core_config
if _is_target_platform(domain):
^^^^^^^^^^^^^^^^^^^^^^^^^^^
File « /esphome/esphome/core/config.py », line 308, in _is_target_platform
return get_component(name, True).is_target_platform
^^^^^^^^^^^^^^^^^^^^^^^^^
File « /esphome/esphome/loader.py », line 230, in get_component
return _lookup_module(domain, exception)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File « /esphome/esphome/loader.py », line 206, in _lookup_module
module = importlib.import_module(f"esphome.components.{domain}")
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File « /usr/local/lib/python3.12/importlib/init.py », line 90, in import_module
return _bootstrap._gcd_import(name[level:], package, level)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File « », line 1387, in _gcd_import
File « », line 1360, in _find_and_load
File « », line 1331, in _find_and_load_unlocked
File « », line 935, in _load_unlocked
File « », line 995, in exec_module
File « », line 1133, in get_code
File « », line 1063, in source_to_code
File « », line 488, in _call_with_frames_removed
File « /config/esphome/esphome_components/ble_client/init.py », line 163
{ ),
^
SyntaxError: closing parenthesis ‹ ) › does not match opening parenthesis ‹ { ›

Yop, je regarde ça quand je rentre de congés ce weekend

Finalement j’ai commit un fix, dis moi si c’est bon pour toi

Bonjour et Merci pour ce travail très intéressant.

Je me suis lancé dernièrement dans l’installation d’une box domotique sur mon lieu de travail pour justement contrôler notre chauffage (via le module bluetooth de Daikin). Je suis sur un Raspberry Pi 5 avec le bluetooth intégré + un disque MVme. J’ai réussi à l’appairage avec le module Daikin (option n°1 du tuto) cependant sur HA lorsque je lance l’installation du module Daikin Madoka je fait face à une erreur :

Erreur

Le flux de configuration n’a pas pu être chargé: {« message »:« Invalid handler specified »}

et le journal indique :

Détails du journal (AVERTISSEMENT)

Enregistreur: annotatedyaml.constructors
Source: util/yaml/loader.py:65
S’est produit pour la première fois: 10:51:13 (1 occurrence)
Dernier enregistrement: 10:51:13

YAML file /config/configuration.yaml contains duplicate key « climate ». Check lines 13 and 14

et

Détails du journal (AVERTISSEMENT)

Enregistreur: homeassistant.util.loop
Source: util/loop.py:137
S’est produit pour la première fois: 10:51:48 (1 occurrence)
Dernier enregistrement: 10:51:48

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 "integration%3A+config For developers, please see LIEN SUPPRIM2 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)”

et enfin

Détails du journal (ERREUR)

Enregistreur: homeassistant.config_entries
Source: config_entries.py:3969
S’est produit pour la première fois: 10:51:48 (2 occurrences)
Dernier enregistrement: 11:55:23

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

j’ai passé beaucoup de temps pour comprendre mais j’atteins la limite de mes compétences.

Avez-vous des idées ou pistes pour m’aider ?

Merci pour votre aide.

Bonjour CDRN !

Tu as en fait deux problèmes distincts :

1. La clé dupliquée dans configuration.yaml — c’est mineur, tu as simplement deux entrées climate: dans ton fichier. Il faut les fusionner en une seule.

2. La vraie erreur de blocage :

cannot import name 'discover' from 'bleak'

Malheureusement c’est un bug connu : la lib pymadoka utilise from bleak import discover, une fonction supprimée depuis bleak 0.20 (remplacée par BleakScanner.discover()). Les versions récentes de Home Assistant embarquent une version de bleak trop récente pour que pymadoka fonctionne encore — tu n’es pas le seul dans ce cas.

La solution la plus propre : passer à l’option 2 du tuto (ESPHome + proxy BLE). Un petit ESP32 (type XIAO, M5Stack Atom Lite…) posé près du Madoka fait le relais BLE vers HA via Ethernet ou WiFi, et ça fonctionne très bien. En bonus, pas de galère avec les dépendances Python ou le Bluetooth du Raspberry Pi :blush:

Bon courage !

Salut, merci pour ce travail qui a du prendre unpaquet de temps.

en revanche je n’arrive pas a installer l’intégration madoka, j’ai essayer de mettre les fichiers les uns apres les autres en utilisant file editor dans homeassistant/custom components/daikin madoka , ca ne marche pas, j’ai aussi essayé avec studio code server, je n’y arrive pas.

comment insérer le fichier au bon emplacement stp car la je sèche ?

je suis sur HA raspberry PI4 sans esphome pour le moment