homebridge-esphome-ts-mdw
v1.1.1
Published
Homebridge 2.0 plugin for ESPHome devices. Bridges ESP32 touch panels and other ESPHome entities to Apple HomeKit.
Maintainers
Readme
homebridge-esphome-ts-mdw
A modern Homebridge 2.0 plugin that bridges ESPHome devices to Apple HomeKit. Built from scratch — not a fork — with full Noise encryption support, ESM-native TypeScript, and a clean Homebridge 2.0 architecture.
What It Does
This plugin connects ESPHome devices running the native API to Apple HomeKit via Homebridge. It auto-discovers devices on your local network using mDNS and maps ESPHome entity types to HomeKit services:
| ESPHome Entity | HomeKit Service |
| --- | --- |
| button | StatelessProgrammableSwitch |
| switch | Switch |
| light | Lightbulb (on/off, brightness, RGB, colour temperature) |
| sensor (temperature) | TemperatureSensor |
| sensor (humidity) | HumiditySensor |
| binary_sensor (motion) | MotionSensor |
| binary_sensor (door/window) | ContactSensor |
| binary_sensor (smoke) | SmokeSensor |
| climate | HeaterCooler |
Primary Use Case: ESP32 Touch Panels
The primary motivation for this plugin is ESP32 multi-button touch panels. All buttons on a single ESPHome device appear as a single HomeKit accessory containing multiple programmable switch tiles — one per button. Each tile can trigger scenes, automations, or control other accessories directly from Apple Home or Siri.
HomeKit Accessory: "Living Room Panel"
├── Button 1 → StatelessProgrammableSwitch (scene: Good Morning)
├── Button 2 → StatelessProgrammableSwitch (scene: Movie Time)
└── Button 3 → StatelessProgrammableSwitch (scene: Good Night)Panels that already publish touch events through ESPHome template switch entities can opt into the same HomeKit button model with statelessSwitches. Listed switch entities are exposed as HomeKit StatelessProgrammableSwitch services and emit a single-press event when the switch reports ON. Unlisted switches keep the normal persistent HomeKit Switch behaviour.
Contributors
This plugin was designed and built by:
- MadDogWarner (@maddogwarner) — project owner, architecture direction, requirements, and testing.
- Claude (Anthropic) — system architecture, technical design, ESPHome/HAP mapping decisions, code review, and documentation.
- Codex (OpenAI) — implementation of all TypeScript source files from the architecture specification.
Requirements
- Homebridge 2.0.0 or later
- Node.js 22 or later
- ESPHome devices running firmware with the native API enabled (port 6053)
- Noise encryption enabled on your ESPHome devices (strongly recommended)
Installation
Via Homebridge Config UI X (recommended)
Search for homebridge-esphome-ts-mdw in the Homebridge plugin catalogue and install from there. The Config UI X interface provides a guided setup form.
Via npm
npm install -g homebridge-esphome-ts-mdwThen restart Homebridge.
ESPHome Device Setup
Enable the Native API with Noise Encryption
Add the following to your ESPHome device's YAML configuration:
api:
encryption:
key: !secret api_encryption_keyGenerate a random 32-byte base64 key with:
openssl rand -base64 32Store the result in your ESPHome secrets.yaml:
api_encryption_key: "YOUR_BASE64_KEY_HERE"Flash the updated firmware to your device. The api.encryption.key value (the base64 string) is what you will enter as encryptionKey in the Homebridge configuration below.
Multi-Button Touch Panel Example
esphome:
name: living-room-panel
esp32:
board: esp32dev
api:
encryption:
key: !secret api_encryption_key
button:
- platform: template
name: "Scene Good Morning"
id: btn_morning
on_press:
- logger.log: "Morning scene triggered"
- platform: template
name: "Scene Movie Time"
id: btn_movie
on_press:
- logger.log: "Movie scene triggered"
- platform: template
name: "Scene Good Night"
id: btn_night
on_press:
- logger.log: "Night scene triggered"All
buttonentities on a single ESPHome device are grouped into one HomeKit accessory automatically. No special configuration is needed.
Homebridge Configuration
Minimal — mDNS auto-discovery only
Add the platform to your Homebridge config.json. With discovery enabled (the default), the plugin will find all ESPHome devices on your network automatically. Any device that requires an encryption key should also be listed under devices so the key can be provided.
{
"platform": "ESPHomeMDW",
"name": "ESPHome"
}Full Configuration Reference
{
"platform": "ESPHomeMDW",
"name": "ESPHome",
"discovery": true,
"reconnectInterval": 30,
"devices": [
{
"host": "living-room-panel.local",
"port": 6053,
"encryptionKey": "YOUR_BASE64_KEY_HERE",
"name": "Living Room Panel",
"statelessSwitches": [
"panel_switch_1",
"panel_switch_2"
],
"entities": {
"include": [],
"exclude": ["internal_button"]
}
},
{
"host": "bedroom-climate.local",
"encryptionKey": "ANOTHER_BASE64_KEY_HERE"
}
]
}Configuration Options
| Option | Type | Default | Description |
| --- | --- | --- | --- |
| name | string | "ESPHome" | Platform display name |
| discovery | boolean | true | Auto-discover ESPHome devices via mDNS (_esphomelib._tcp) |
| reconnectInterval | integer (seconds) | 30 | Base reconnect delay. Doubles on each failure, capped at 5 minutes |
| devices | array | [] | List of devices to connect explicitly |
Per-Device Options
| Option | Type | Required | Description |
| --- | --- | --- | --- |
| host | string | Yes | Hostname (e.g. panel.local) or IP address of the ESPHome device |
| port | integer | No | Native API port. Defaults to 6053 |
| encryptionKey | string | Recommended | Base64-encoded 32-byte PSK from api.encryption.key in your ESPHome YAML |
| name | string | No | Override the accessory display name in HomeKit |
| statelessSwitches | string[] | No | Switch entity objectId values to expose as HomeKit StatelessProgrammableSwitch buttons instead of persistent Switch accessories |
| entities.include | string[] | No | Whitelist of entity objectId values to expose. If empty, all entities are included |
| entities.exclude | string[] | No | List of entity objectId values to hide from HomeKit |
Stateless Template Switches
For touch panels where ESPHome LVGL buttons drive template switches, list those switch objectId values in statelessSwitches:
{
"platform": "ESPHomeMDW",
"name": "ESPHome",
"devices": [
{
"host": "touch-panel.local",
"port": 6053,
"encryptionKey": "YOUR_BASE64_KEY_HERE",
"name": "Touch Panel",
"statelessSwitches": [
"panel_switch_1",
"panel_switch_2"
]
}
]
}Each listed switch appears in Apple Home as a stateless programmable button. The plugin emits SINGLE_PRESS only when ESPHome reports the switch ON, so a momentary ON then OFF pulse creates one HomeKit button press and does not leave a HomeKit switch state behind.
Entity Filtering
Entity filters use the ESPHome entity objectId — the snake_case identifier generated from the entity name. For example, an entity named "Scene Good Morning" has objectId scene_good_morning.
"entities": {
"include": ["btn_morning", "btn_movie"],
"exclude": []
}Setting include to a non-empty list exposes only those entities. exclude hides specific entities and is applied after include.
Encryption
This plugin uses Noise encryption (Noise_NNpsk0_25519_ChaChaPoly_SHA256), the same protocol ESPHome uses in its own app and dashboard. The pre-shared key (PSK) is the base64 string from api.encryption.key in your ESPHome YAML.
Note: Legacy plaintext API password authentication (
api.password) is not supported by the underlyingesphome-clientlibrary (v1.3.0). All devices should use Noise encryption. Devices configured without anencryptionKeywill attempt a plaintext connection with no authentication.
Reconnection
If a device goes offline, the plugin automatically reconnects using exponential backoff:
- First retry after
reconnectIntervalseconds (default 30 s) - Each subsequent failure doubles the delay
- Maximum delay is 5 minutes
The delay resets to the base interval on successful reconnection.
Troubleshooting
Devices not appearing in HomeKit
- Confirm the ESPHome native API is enabled in the device YAML (
api:block present) - Check the device is reachable:
ping living-room-panel.local - Verify the
encryptionKeymatchesapi.encryption.keyin your ESPHome YAML exactly - Enable Homebridge debug logging and look for
[ESPHome]log lines
Buttons not triggering automations
- Ensure the button entity fires a press event in ESPHome logs
- In Apple Home, open the tile and confirm the automation is set to trigger on "Single Press"
ProgrammableSwitchEventis event-only — it does not have a readable state, which is expected behaviour
Template switches turn on but not off
- Update to version 1.0.4 or later if ESPHome template switches turn ON in Homebridge/HomeKit but do not return to OFF after a second device-side touch.
- ESPHome may omit default boolean
falsevalues from switch state telemetry; current versions normalise a keyed switch event withoutstateto OFF. - Enable Homebridge debug logging and confirm the switch state update line shows
OFFfor the affected entity.
Light colour changes not working
- RGB and colour temperature are sent as separate commands; confirm the ESPHome light entity supports the relevant colour mode
- Check ESPHome logs for any rejected light commands
Device keeps reconnecting
- If the encryption key is wrong the connection will be refused — verify the key
- Check for network issues or DHCP address changes; using
.localmDNS hostnames is more reliable than IP addresses
Building from Source
git clone https://github.com/maddogwarner/homebridge-esphome-ts-mdw.git
cd homebridge-esphome-ts-mdw
npm install
npm run build # compiles TypeScript to dist/
npm run lint # ESLint check