https://github.com/mtheli/philips_sonicare_ble
Philips Sonicare BLE integration for Home Assistant — real-time brushing data via Bluetooth or ESP32 bridge
https://github.com/mtheli/philips_sonicare_ble
ble bluetooth esphome hacs home-assistant philips sonicare
Last synced: about 1 month ago
JSON representation
Philips Sonicare BLE integration for Home Assistant — real-time brushing data via Bluetooth or ESP32 bridge
- Host: GitHub
- URL: https://github.com/mtheli/philips_sonicare_ble
- Owner: mtheli
- License: mit
- Created: 2026-03-18T07:21:46.000Z (3 months ago)
- Default Branch: master
- Last Pushed: 2026-04-30T22:42:58.000Z (about 1 month ago)
- Last Synced: 2026-04-30T23:13:49.928Z (about 1 month ago)
- Topics: ble, bluetooth, esphome, hacs, home-assistant, philips, sonicare
- Language: Python
- Size: 861 KB
- Stars: 14
- Watchers: 2
- Forks: 1
- Open Issues: 3
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# Philips Sonicare BLE Integration for Home Assistant
[](https://github.com/hacs/integration)
[](https://github.com/mtheli/philips_sonicare_ble/releases)
[](LICENSE)
This is a custom component for Home Assistant to integrate **Philips Sonicare BLE toothbrushes**.
The integration connects to your toothbrush via **Bluetooth Low Energy (BLE)** to provide battery status, brushing session data, brush head wear tracking, and more. All communication is fully local -- no cloud, no app required.

Three connection methods are supported:
1. **Direct Bluetooth** -- connects from the HA host's Bluetooth adapter (built-in or USB). Uses a persistent live connection with a poll fallback.
2. **ESP32 BLE Bridge** ★ -- an ESP32 running a custom ESPHome component acts as a wireless BLE relay. Recommended for out-of-range setups; supports multiple devices and notification throttling.
3. **Bluetooth Proxy** -- uses an existing ESPHome `bluetooth_proxy`. Works for a single Sonicare per proxy; not recommended due to stability issues.
See [Configuration](#configuration) for setup instructions.
---
## Table of Contents
- [Tested Models](#tested-models)
- [Dashboard Card](#dashboard-card)
- [Features](#features)
- [Prerequisites](#prerequisites)
- [Installation](#installation)
- [Configuration](#configuration)
- [Option A: Direct Bluetooth](#option-a-direct-bluetooth)
- [Option B: ESP32 BLE Bridge](#option-b-esp32-ble-bridge)
- [Option C: Bluetooth Proxy](#option-c-bluetooth-proxy)
- [How It Works](#how-it-works)
- [Troubleshooting & Caveats](#troubleshooting--caveats)
- [BLE Protocol](#ble-protocol)
- [Screenshots](#screenshots)
---
## Tested Models
| Model | Direct BLE | ESP32 Bridge | Tested by |
| :--- | :---: | :---: | :--- |
| **Sonicare For Kids** | | | |
| [HX6340](https://www.usa.philips.com/c-p/HX6351_41/sonicare-for-kids-sonic-electric-toothbrush) | :white_check_mark: | :white_check_mark: | Maintainer |
| HX6322, HX6352 | :white_check_mark: | — | Community ([GrumpyMeow#14](https://github.com/GrumpyMeow/sonicare-ble-hacs/issues/14#issuecomment-4258415247)) |
| **FlexCare Platinum Connected** | | | |
| HX9120 | *not yet tested* | *not yet tested* | — |
| **ExpertClean** | | | |
| [HX962V](https://www.usa.philips.com/c-m-pe/dental-professionals/products/tooth-brushes/expertclean) (ExpertClean 7500) | — | :white_check_mark: | Community ([#1](https://github.com/mtheli/philips_sonicare_ble/issues/1)) |
| HX960V (ExpertClean 7300) | :white_check_mark: | :white_check_mark: | Maintainer |
| **DiamondClean Smart** | | | |
| [HX992X](https://www.usa.philips.com/c-p/HX9903_11/sonicare-diamondclean-smart-9300-sonic-electric-toothbrush-with-app) | :white_check_mark: | :white_check_mark: | Maintainer |
| **DiamondClean 9000** | | | |
| [HX960X](https://www.philips.de/c-p/HX9601_03/sonicare-diamondclean-9000-elektrische-zahnbuerste-mit-app) | :white_check_mark: | — | Community ([#9](https://github.com/mtheli/philips_sonicare_ble/issues/9)) |
| HX991B | :white_check_mark: | — | Community ([#9](https://github.com/mtheli/philips_sonicare_ble/issues/9#issuecomment-4321642114)) |
| [HX991M](https://www.usa.philips.com/c-p/HX9911_91/9000-series-sonic-electric-toothbrush) | :white_check_mark: | :white_check_mark: | Community ([forum](http://community-smarthome.com/t/philips-sonicare-ble-zahnbuerste-in-home-assistant-mit-echtzeit-sensoren/10555/17), [#7](https://github.com/mtheli/philips_sonicare_ble/issues/7)) |
| **DiamondClean Prestige** | | | |
| [HX999X](https://www.usa.philips.com/c-p/HX9990_11/sonicare-9900-prestige-power-toothbrush-with-senseiq) | :white_check_mark: | :white_check_mark: | Maintainer, Community ([forum](https://community.home-assistant.io/t/philips-sonicare-ble-toothbrush-integration-with-30-sensors/999515/5)) |
| **Series 7100** | | | |
| [HX742X](https://www.usa.philips.com/c-p/HX7423_43/sonicare-7100) | :white_check_mark: | *not yet tested* | Community ([#4](https://github.com/mtheli/philips_sonicare_ble/issues/4)) |
Any BLE-enabled Philips Sonicare toothbrush using either the standard protocol or the newer Condor protocol (HX742X / Series 7100) should work (Sonicare For Kids, ExpertClean, DiamondClean Smart, DiamondClean 9000, DiamondClean Prestige, Series 7100, and more). The integration auto-discovers compatible devices via BLE and selects the right protocol automatically. If you have a different model — happy to hear your test results!
> [!NOTE]
> Some models (ExpertClean, HX991M, DiamondClean Prestige, Series 7100) require **BLE bonding**. The integration detects this automatically and pairs the device during setup. Models like DiamondClean Smart and Sonicare For Kids use open GATT and connect without pairing. Series 7100 brushes additionally use a **rolling private address (RPA)** that changes every few minutes — bonding is what allows the host to follow the brush across address rotations.
---
## Dashboard Card
For a visual brushing dashboard, use the [**Toothbrush Card**](https://github.com/mtheli/toothbrush-card) -- a custom Lovelace card with live sector tracking, pressure display, and brush head wear indicator. Works with both Philips Sonicare and Oral-B toothbrushes.

---
## Features
This integration creates a new device for your toothbrush and provides the following entities based on your device's hardware:
### Main Status
| Entity | Type | Description |
| :--- | :--- | :--- |
| **Handle State** | Sensor | Current state (`Off`, `Standby`, `Running`, `Charging`, `Shutdown`). |
| **Activity** | Sensor | Composite state derived from handle + brushing state (`Off`, `Standby`, `Brushing`, `Paused`, `Charging`). |
| **Brushing State** | Sensor | Detailed brushing status (`Off`, `On`, `Pause`, `Session Complete`, `Session Aborted`). |
| **Brushing Mode** | Sensor | Active cleaning mode (`Clean`, `White+`, `Gum Health`, `Deep Clean+`, `Sensitive`, `Tongue Care`). |
| **Intensity** | Sensor | Current intensity level (`Low`, `Medium`, `High`). |
| **Battery Level** | Sensor | Battery charge level (`%`). |
| **Brushing** | Binary Sensor | Indicates if actively brushing. |
| **Charging** | Binary Sensor | Indicates if currently charging. |
| **Pressure Alert** | Binary Sensor | Indicates if too much pressure is applied (during brushing). |
### Controls
| Entity | Type | Description |
| :--- | :--- | :--- |
| **Brushing Mode** | Select | Set the brushing mode for the next session. Only created on models that support BLE mode writes (DiamondClean Prestige HX999X, HX9996, and HX74xx series). BrushSync-enabled models like DiamondClean Smart HX992X use brush-head-based mode selection and do not get this entity. |
### Sensor Data (live during brushing)
These sensors are only available while actively brushing and stream live data from the toothbrush IMU.
| Entity | Type | Description |
| :--- | :--- | :--- |
| **Pressure** | Sensor | Brushing pressure force (`g`). |
| **Pressure State** | Sensor | Pressure classification (`No Contact`, `Optimal`, `Too High`). |
| **Temperature** | Sensor | Handle temperature (`°C`). |
### Brushing Session
| Entity | Type | Description |
| :--- | :--- | :--- |
| **Brushing Time** | Sensor | Current session brushing time (seconds). |
| **Routine Length** | Sensor | Target brushing duration (typically 120s). |
| **Session ID** | Sensor | Current brushing session identifier. |
| **Latest Session ID** | Sensor | Most recently completed session identifier. |
| **Session Count** | Sensor | Total number of stored sessions. |
### Brush Head
| Entity | Type | Description |
| :--- | :--- | :--- |
| **Brush Head Wear** | Sensor | Brush head wear level (`%`, computed from usage/lifetime limit). |
| **Brush Head Usage** | Sensor | Accumulated brush head usage counter. |
| **Brush Head Limit** | Sensor | Maximum brush head lifetime. |
| **Brush Head Type** | Sensor | Brush head type (`Adaptive Clean`, `Adaptive White`, `Tongue Care`, `Adaptive Gums`, `Sensitive`). |
| **Brush Head Serial** | Sensor | Brush head serial number (from NFC tag). |
| **Brush Head Date** | Sensor | Brush head manufacturing date. |
| **Brush Head Ring ID** | Sensor | Color ring identifier (for family brush head tracking). |
| **Brush Head NFC Version** | Sensor | NFC chip version on the brush head. |
| **Brush Head Payload** | Sensor | Raw NFC payload data (hex). |
### Diagnostics
| Entity | Type | Description |
| :--- | :--- | :--- |
| **Motor Runtime** | Sensor | Cumulative motor runtime (seconds). |
| **Handle Time** | Sensor | Total handle operating time since manufacture (seconds). |
| **Model Number** | Sensor | Device model number (e.g., HX992B). |
| **Firmware** | Sensor | Installed firmware version. |
| **Last Seen** | Sensor | Timestamp of last successful data read. |
| **RSSI** | Sensor | BLE signal strength in dBm (Direct BLE only). |
| **Adapter** | Sensor | Name/path of the BLE adapter currently carrying the link (e.g. `hci0`, ESP device name + bridge slot). |
| **Adapter Type** | Sensor | Transport classification: *Direct Bluetooth* / *ESP Bridge* / *Bluetooth Proxy*. |
| **Bridge Version** | Sensor | ESP bridge firmware version (ESP Bridge only). |
| **Bridge Last Boot** | Sensor | Timestamp the ESP bridge last booted — useful for detecting unattended reboots (ESP Bridge only). |
| **ESP Bridge** | Binary | Whether the ESP bridge is reachable and sending heartbeats (ESP Bridge only). |
| **BLE Connected** | Binary | Whether the GATT link to the toothbrush is currently up. |
---
## Prerequisites
* A compatible Philips Sonicare toothbrush (see [Tested Models](#tested-models) above).
* **Either** a Home Assistant instance with the **Bluetooth integration** enabled and a working Bluetooth adapter, **or** an ESP32 running the [BLE Bridge component](docs/ESP32_BRIDGE.md).
* **Pairing depends on the model** -- DiamondClean Smart (HX992X) uses open GATT without bonding. ExpertClean (HX962X), DiamondClean Prestige (HX999X), HX991M, and Series 7100 (HX742X) require BLE pairing. The integration handles both cases automatically. Series 7100 brushes additionally use a rolling private address (RPA) that changes every few minutes — bonding lets the host follow the brush across address rotations. Simply close any Sonicare phone app to free the BLE connection.
> [!NOTE]
> The toothbrush only advertises via BLE for a short time after being picked up from the charger or turned on/off. It enters deep sleep after approximately 20 seconds of inactivity. While on the charging stand, it is **not reachable** via BLE.
---
## Installation
### HACS (Recommended)
> Don't have HACS yet? Follow the [HACS installation guide](https://hacs.xyz/docs/use/) first.
1. Go to **HACS** > **Integrations** in your Home Assistant UI.
2. Click the three-dot menu in the top right and select **Custom repositories**.
3. Add `https://github.com/mtheli/philips_sonicare_ble` and select the category **Integration**.
4. Find the "Philips Sonicare" integration and click **Install**.
5. Restart Home Assistant.
### Manual Installation
1. Copy the `custom_components/philips_sonicare_ble` directory from this repository into your Home Assistant `config/custom_components/` folder.
2. Restart Home Assistant.
---
## Configuration
The integration supports three connection methods:
| | Method | Best for |
| :--- | :--- | :--- |
| **[Option A](#option-a-direct-bluetooth)** ⭐ | **Direct Bluetooth** | HA host within Bluetooth range of the toothbrush (typically 5–10 m / 15–30 ft) |
| **[Option B](#option-b-esp32-ble-bridge)** ⭐ | **ESP32 BLE Bridge** | **Recommended for out-of-range setups.** Multiple devices, live data streaming, full notification throttling |
| **[Option C](#option-c-bluetooth-proxy)** | **Bluetooth Proxy** | Single Sonicare, existing `bluetooth_proxy` — **not recommended, see warning below** |
⭐ = recommended path
> [!IMPORTANT]
> Both proxy/bridge paths on ESP32 are affected by an [ESP-IDF bug](https://github.com/esphome/esphome/issues/15783) that crashes GATT service discovery. A [compile-time workaround](docs/KNOWN_ISSUES.md#workaround) is available and required until ESP-IDF v5.5.5 ships (expected mid-May 2026).
### Option A: Direct Bluetooth
1. Wake up your toothbrush (pick it up from the charger or briefly turn it on).
2. Navigate to **Settings > Devices & Services**.
3. The toothbrush should appear under **Discovered** -- click **Configure**.
- If not discovered automatically, click **+ Add Integration**, search for "**Philips Sonicare**", and enter the MAC address manually.
4. The confirmation dialog shows the current brush status and detected services. Make sure the toothbrush is **turned on** (status shows "Active") before clicking **Submit**.
> [!TIP]
> Some models (ExpertClean, HX991M, DiamondClean Prestige, Series 7100) require BLE bonding -- the integration detects this automatically and pairs the device during setup via D-Bus. If auto-pairing is not available (e.g. HAOS without D-Bus), manual pairing instructions are shown. Series 7100 brushes also rotate their advertised BLE address (RPA) every few minutes, so an unbonded brush appears under different MACs on each scan; bonding pins it to a stable identity. Simply close the Sonicare phone app to free the BLE connection.
### Option B: ESP32 BLE Bridge
If your Home Assistant host is too far from the toothbrush for a direct Bluetooth connection, you can use an [ESP32](https://esphome.io/components/esp32.html) as a wireless BLE bridge. The ESP32 connects to the toothbrush and relays data to HA over WiFi.
This is **not** a standard ESPHome Bluetooth Proxy -- it is a dedicated component that manages the BLE connection directly on the ESP32 and provides full read/write/subscribe access to all GATT characteristics.
> [!NOTE]
> This option requires basic [ESPHome](https://esphome.io/) knowledge (flashing firmware, editing YAML configs). If you're new to ESPHome, check out [Getting Started with ESPHome](https://esphome.io/guides/getting_started_hassio) first.
For the complete setup guide, see **[ESP32 Bridge Setup Guide](docs/ESP32_BRIDGE.md)**.
### Option C: Bluetooth Proxy
> [!WARNING]
> **Due to stability issues, it is strongly recommended to use [Option B: ESP32 BLE Bridge](#option-b-esp32-ble-bridge) instead of a Bluetooth Proxy.** Proxy setups have shown silent-connection issues — after a brushing session a device can stop delivering notifications while the proxy itself stays reachable, requiring a Home Assistant integration reload to recover. The proxy also has no notification throttling, which overloads the WiFi socket queue when more than one Sonicare is connected to the same proxy.
>
> This option is documented for users who already operate a proxy for other devices and only have a single Sonicare.
If you already operate an [ESPHome Bluetooth Proxy](https://esphome.io/components/bluetooth_proxy.html), the integration can use it as a relay — no dedicated bridge firmware needed. Confirmed working on ESPHome 2026.2+ with `io_capability: none`.
**Known caveats:**
- **Proxy firmware needs the [Bluedroid workaround](docs/KNOWN_ISSUES.md#workaround)** until ESP-IDF v5.5.5 is released.
- **First connect after reboot takes ~5 s longer** than Direct BLE. Bond keys live on the proxy's NVS, so service discovery and re-encryption race briefly; the integration retries reads automatically during this window.
- **Notify Throttle option has no effect on Proxy** — throttling is only implemented in the ESP32 BLE Bridge firmware.
- **RSSI shown in the Connection device reflects the scanner actually carrying the link** (since v0.9.x), not the strongest advertisement.
Setup is the same as Option A — Home Assistant's Bluetooth stack picks the proxy automatically when it has a better or equal connection score to the host adapter.
### Options
| Option | Default | Description |
| :--- | :--- | :--- |
| Pressure Sensor | Enabled | Stream live pressure data during brushing. |
| Temperature Sensor | Enabled | Stream live temperature data during brushing. |
| Gyroscope Sensor | Disabled | Stream live 6-axis IMU data during brushing (experimental). |
| Notify Throttle | 500ms | Minimum interval between BLE notification updates (ESP Bridge only, 100-5000ms). |
---
## How It Works
### Connection Behavior
The Sonicare toothbrush has unique BLE behavior compared to other Philips devices:
* **Slow advertising** -- the toothbrush sends BLE advertisements only every 10-30 seconds (most BLE devices: every 100-500ms).
* **Short wake window** -- after turning off, the toothbrush stays connectable for only ~20 seconds before entering deep sleep.
* **Pairing varies by model** -- DiamondClean Smart (HX992X) uses open GATT without bonding. ExpertClean (HX962X), DiamondClean Prestige (HX999X), HX991M, and Series 7100 (HX742X) require BLE pairing. The integration handles both cases automatically. Series 7100 brushes also use rolling private addresses (RPA) that change every few minutes — bonding pins them to a stable identity that survives the rotations.
The integration handles this with:
1. **Advertisement-triggered reconnect** -- a BLE advertisement callback immediately wakes the connection thread, eliminating unnecessary backoff delays.
2. **Subscribe-first pattern** -- after connecting, BLE notification subscriptions are established immediately (before reading data). This keeps the connection alive because the toothbrush stays awake as long as active subscriptions exist.
3. **Smart lock management** -- the polling fallback yields to the live monitoring thread when an advertisement is detected, preventing connection contention.
### Data Flow
```
Toothbrush wakes up
--> BLE Advertisement detected by HA
--> Integration connects (~6s BLE stack overhead)
--> Subscribe to 13 notification characteristics (~1s)
--> Read all characteristics (~3s)
--> Live updates flow until toothbrush sleeps
--> Disconnect detected --> wait for next advertisement
```
---
## Troubleshooting & Caveats
* **Toothbrush not discovered**: Wake it up by picking it up from the charger or briefly turning it on. The toothbrush is not reachable via BLE while on the charging stand.
* **Slow connection**: The toothbrush advertises every 10-30 seconds. The integration connects as soon as the first advertisement is received, but the BLE stack adds ~6 seconds overhead.
* **Connection drops quickly**: This is normal when the toothbrush is idle. It sleeps after ~20 seconds. The integration will reconnect automatically on the next wake.
* **Phone app conflict**: The toothbrush supports only one BLE connection. Close or uninstall the Sonicare phone app if you experience connection issues.
* **Pairing issues**: If a model that requires bonding won't connect, remove the toothbrush from your phone's Bluetooth settings first (Settings → Bluetooth → Philips Sonicare → Forget/Unpair). The integration handles stale bonds automatically, but the phone's bond may block the connection.
* **ESPHome Bluetooth Proxy**: Works with the [Bluedroid NULL-check patch](docs/KNOWN_ISSUES.md#workaround) until ESP-IDF v5.5.5 ships. See [Option C](#option-c-bluetooth-proxy) for scope and the dedicated [ESP32 BLE Bridge](docs/ESP32_BRIDGE.md) as the recommended alternative for multi-device setups.
* **Unsure if your model is compatible?** Run the [GATT scan script](scripts/sonicare_scan.py) to check which BLE protocol your toothbrush uses. It only needs Python 3 and `bleak` (`pip install bleak`).
### Known Issues
* **Direct BLE reconnect may be delayed**: Home Assistant's Bluetooth stack filters duplicate advertisements. Since the Sonicare sends identical advertisement data on every broadcast, wake-ups can be missed. The integration uses a D-Bus RSSI listener as a workaround, but reconnects may still take longer than expected. See [habluetooth#397](https://github.com/Bluetooth-Devices/habluetooth/discussions/397) for the upstream discussion.
---
## BLE Protocol
The integration communicates directly via BLE -- no cloud, no app required. All communication is fully local.
The toothbrush exposes multiple GATT services with individual characteristics for each data point (battery, brushing state, pressure, brush head, etc.). Data is read directly from these characteristics and live updates are received via GATT notifications.
For a detailed technical description of the BLE protocol including service UUIDs, characteristic reference, data formats, and enum values, see [BLE_PROTOCOL.md](docs/BLE_PROTOCOL.md).
---
## Screenshots
| Sensors & Controls | Diagnostics | Dashboard Card |
| :---: | :---: | :---: |
|  |  |  |
| Device Overview | Brush Head |
| :---: | :---: |
|  |  |
---
## Disclaimer
This is an independent community project and is not affiliated with, endorsed by, or sponsored by Philips. All product names, trademarks, and registered trademarks are property of their respective owners.
## License
[MIT](LICENSE)