Control your Tedee smart lock over Bluetooth Low Energy directly from Home Assistant.
What you need:
- Home Assistant 2024.1.0 or newer
- A Tedee GO 2 lock (aluminium or plastic) - other models (GO, PRO) may work but are untested
- A Bluetooth adapter on your Home Assistant host (built-in or USB dongle), or an ESPHome Bluetooth Proxy (ESP32) within BLE range of the lock (~10m, varies by environment)
- A free Tedee Personal Access Key from the Tedee Portal (used during setup for certificate registration)
What you don't need:
- A Tedee Bridge - the integration talks directly to the lock over BLE
- A permanent cloud connection - after initial setup, all lock commands happen locally. The cloud is only contacted every few days to refresh certificates
- Lock, Unlock, and Open - Full lock control including pull spring (open latch) support. The lock entity also surfaces transitional states like
locking,unlocking,partially_unlocked, andjammed - Auto-pull on unlock - Optional setting; when disabled, the integration sends
UNLOCK_NO_PULLso the lock-side auto-pull setting (configured in the Tedee mobile app) is overridden — the Home Assistant toggle is the single source of truth for spring-pull behavior - Door open/closed sensor - If your Tedee lock has the optional door sensor accessory installed, the integration exposes a binary sensor showing whether the door is open or closed
- Battery monitoring - Battery percentage as a sensor, with a
chargingattribute exposed alongside it - Real-time state updates - Lock state changes are pushed instantly via BLE notifications, with a 10-minute polling safety net and a 45-second keep-alive
- Jam detection - The lock reports if it gets jammed during locking or unlocking
- Activity tracking - See who triggered the last action and how (see details below)
- Firmware-update awareness - Firmware version shown on the device page; a diagnostic binary sensor turns on when an update is available (per cloud API) or while the lock is actively applying one. The lock entity exposes
is_updating: trueduring a flash and rejectslock/unlock/openservice calls so automations don't fire mid-update - Persistent connection with auto-reconnect - The integration maintains a live BLE connection and automatically reconnects if it drops (forever, with backoff), with a grace period that hides brief reconnections from the UI
- MAC-address auto-recovery - If a firmware update changes the lock's BLE MAC, the integration rediscovers it by service UUID and updates the stored address — no reconfigure or HA restart needed
- Direct BLE and ESPHome Bluetooth Proxy - Connect directly from your Home Assistant host's Bluetooth adapter, or route through an ESPHome Bluetooth Proxy for extended range
- Custom dashboard card - A built-in Lovelace card with animated status icons, smart action buttons, and at-a-glance info
- Open HACS in Home Assistant
- Search for Tedee BLE and install
- Restart Home Assistant
- Copy the
custom_components/tedee_blefolder to your Home Assistantconfig/custom_components/directory - Restart Home Assistant
- Go to Settings > Devices & Services > Add Integration
- Search for Tedee BLE
- Enter your Tedee Personal Access Key (how to get one)
- Select your lock from the list
- The integration will scan for the lock over BLE - make sure your HA host has Bluetooth (or an ESPHome proxy) and is in range
- If the scan doesn't find it, you can enter the BLE MAC address manually
The integration needs a Tedee Personal Access Key to register with the lock and obtain BLE certificates. After setup, all lock commands happen locally over BLE - the cloud API is only contacted every few days to refresh certificates. Your API key never leaves your Home Assistant instance.
- Go to Tedee Portal and log in
- Navigate to Personal Access Keys
- Create a new key with the following scopes:
- Device.Read - discover your locks
- DeviceCertificate.Operate - obtain BLE certificates
- Mobile.ReadWrite - register as a mobile device
- DeviceActivity.Read - read activity logs for user identification
- Copy the key - you'll paste it during integration setup
After setup, click the Configure button on the integration to adjust:
- Auto-pull on unlock - When enabled, unlocking the lock will also automatically pull the spring to unlatch the door. When disabled, the integration sends
UNLOCK_NO_PULLto the lock, which overrides the lock-side auto-pull setting from the Tedee mobile app — so the HA toggle alone determines whether the spring pulls. Takes effect on the next unlock; no reload required.
The integration creates the following entities per lock, all grouped under a single device:
| Entity | Type | Description |
|---|---|---|
| Lock | lock |
Lock, unlock, and open (pull spring). Surfaces locking, unlocking, partially_unlocked, and jammed states. Exposes last_action, last_trigger, last_user, and is_updating as attributes. |
| Door | binary_sensor |
Door open/closed state. Requires the optional Tedee door sensor accessory to be installed on the lock. |
| Battery | sensor |
Battery percentage. Exposes a charging attribute (diagnostic). |
| Firmware update | binary_sensor |
On while a firmware update is available or being applied. Exposes a status attribute (available / updating / idle). Diagnostic. |
Each lock-state change also fires a tedee_ble_lock_action event on the bus (with action, trigger, user, entity_id, lock_name) — useful for automations and the logbook.
The firmware version is shown on the device info page (Settings > Devices > your lock), not as a separate entity.
The lock entity exposes three attributes describing the most recent state change:
last_action- the resulting state, e.g.locked,unlocked,partially_unlocked,pulling. The dashboard card uses this to render fine-grained states the corelockdomain doesn't surface.last_triggertells you how it was triggered. The exact tokens you'll see in the attribute are:button- physical button press on the lockremote- BLE command from Home Assistant or phonekeypad- PIN entry on a connected Tedee Keypadauto_lock- the lock's built-in auto-lock timerauto_unlock/auto- auto-unlock featuredoor_sensor- triggered by opening or closing the doormanual- turned by hand
last_usertells you who triggered it, resolved from a user ID to a name. The integration builds a mapping of user IDs to names from the Tedee Cloud API activity logs (including keypad PIN aliases). This map is automatically refreshed during periodic certificate renewals and whenever an unknown user is detected, so new shares are picked up without any manual action.
The integration ships with a built-in Tedee Lock Card that shows everything in a single compact row:
The card is auto-registered on startup - no need to add it as a resource manually.
type: custom:tedee-lock-card
lock: lock.lock_lock
door: binary_sensor.lock_door # optional
battery: sensor.lock_battery # optional
event: sensor.lock_last_event # optional — entity opened when the activity row is clicked
name: Front Door # optional, overrides entity name
show_activity: true # optional, default true — set false to hide the activity rowWhat it shows:
- State-colored lock icon (green = locked, amber = unlocked, blue = transitioning, red = jammed, purple = applying firmware update, grey = unavailable)
- Animated icon (pulse during locking/unlocking and during firmware update, shake when jammed)
- Smart buttons - only shows actions that make sense (e.g. "Open" only appears when unlocked); all buttons are hidden while the lock is applying a firmware update
- Door state and battery chips - click to open their respective entity dialogs
- Last user and trigger source (button press, remote command, auto-lock, door sensor)
Direct BLE connection — Home Assistant talks to the lock directly over encrypted BLE; an HTTPS path to the Tedee Cloud API is used only for the periodic certificate refresh.
flowchart LR
HA["Home Assistant"]
Lock["Tedee Lock<br/>(GO 2)"]
Cloud["Tedee Cloud API"]
HA <-->|"BLE (encrypted)"| Lock
HA -.->|"HTTPS<br/>(cert refresh, every few days)"| Cloud
Via ESPHome Bluetooth Proxy — Home Assistant reaches an ESP32 BLE proxy over Wi-Fi, which relays encrypted BLE to the lock. The cloud path is unchanged.
flowchart LR
HA["Home Assistant"]
Proxy["ESPHome BLE Proxy<br/>(ESP32)"]
Lock["Tedee Lock<br/>(GO 2)"]
Cloud["Tedee Cloud API"]
HA <-->|"Wi-Fi"| Proxy
Proxy <-->|"BLE (encrypted)"| Lock
HA -.->|"HTTPS<br/>(cert refresh, every few days)"| Cloud
- Device Registration - The integration registers with Tedee's Cloud API and obtains a signed certificate for BLE authentication
- BLE Discovery - The integration scans for your lock over Bluetooth (directly or through an ESPHome proxy), filtering by the lock's service UUID derived from its serial number
- Encrypted Session - A secure, encrypted BLE session is established using the certificate
- Persistent Connection - The integration maintains a persistent BLE connection with a 45-second keep-alive (the lock drops idle connections after ~25-45 s of inactivity), plus a 10-minute polling safety net
- Real-time Notifications - Lock state changes are pushed instantly via BLE notifications
- Automatic Reconnection - If the BLE connection drops, the integration reconnects automatically with backoff: 2 s, 5 s, 10 s, 30 s, then every 60 s thereafter (forever). A 15-second grace period prevents brief reconnections from showing entities as "unavailable"
- MAC-Address Recovery - If the lock's BLE MAC changes (e.g. after a firmware update), the integration first checks Home Assistant's discovery cache by service UUID; if that misses, it falls back to a live active BLE scan and silently updates the stored address. No HA restart or reconfigure is required
- Make sure Bluetooth is enabled on your HA host
- Move the HA host closer to the lock
- Check that no other device (phone, bridge) is monopolizing the BLE connection
- You can enter the MAC address manually if the scan fails
- The Tedee lock (especially the GO model) drops idle BLE connections after ~25-45 seconds. This is normal battery-saving behavior. The integration reconnects automatically in ~2-5 seconds, and a grace period prevents entities from briefly showing as "unavailable"
- If entities stay unavailable for longer periods, check BLE range - move the HA host closer, or use an ESPHome Bluetooth Proxy placed near the lock
- Interference from other 2.4GHz devices (Wi-Fi, Zigbee) can cause disconnections
- Firmware updates put the lock offline for a few minutes and can change its BLE MAC. The integration's reconnect loop will pick it up automatically once the lock starts advertising again
- If you don't want to wait, click Reload on the Tedee BLE integration (Settings → Devices & Services → Tedee BLE → ⋯ → Reload). This re-runs setup, which rediscovers the lock by service UUID and updates the stored MAC
- The integration auto-refreshes certificates. If you see persistent errors, remove and re-add the integration
If you run into a problem, please open an issue and include the following:
- Lock model and firmware version (e.g. Tedee GO 2, firmware 2.4.18050)
- Connection type - direct Bluetooth or ESPHome Bluetooth Proxy (and if proxy, the ESP32 board model)
- Debug logs - enable debug logging by adding this to your
configuration.yamland restarting:Then reproduce the issue and include the relevant log output from Settings > System > Logs.logger: default: info logs: custom_components.tedee_ble: debug
- Steps to reproduce - what you did before the issue occurred
The repo includes a standalone cli.py for testing and debugging the BLE connection outside of Home Assistant. It uses the same underlying library as the integration and supports both direct Bluetooth and ESPHome proxy.
python cli.py scan # Find Tedee locks nearby
python cli.py register # One-time: generate keys and register with Tedee cloud
python cli.py status # Get lock state and battery
python cli.py lock # Lock the door
python cli.py unlock [--force] [--pull] # Unlock (--pull to also pull spring)
python cli.py pull # Pull spring only
python cli.py info [--raw] # Show lock model, serial, firmware from cloud
python cli.py shell # Interactive session with persistent connection
# Via ESPHome Bluetooth Proxy
python cli.py --proxy 192.168.1.50 scan
python cli.py --proxy 192.168.1.50 shellMIT License - see LICENSE
