Manage your Tesla vehicle over BLE using an ESP32 — control charging, check battery status, lock/unlock, and more.
| Controls | Sensors | Diagnostic |
|---|---|---|
- Pick your install method:
| Method | How | Best for |
|---|---|---|
| ESPHome Device Builder | Create a project, then add the board package | HA + ESPHome add-on users |
| CLI | make compile && make upload |
Users with Python + uv |
Pick the config for your hardware:
| Board | Device Builder package | CLI |
|---|---|---|
| M5Stack NanoC6 | tesla-ble-m5stack-nanoc6.dashboard.yml |
BOARD=m5stack-nanoc6 |
| M5Stack AtomS3 | tesla-ble-m5stack-atoms3.dashboard.yml |
BOARD=m5stack-atoms3 |
| Generic ESP32 | tesla-ble-esp32-generic.dashboard.yml |
BOARD=esp32-generic |
Other boards? Copy one from boards/ and set it with BOARD=<your-board>.
The generic package targets the classic ESP32 (esp32dev). Do not use it for ESP32-C3, ESP32-S3, or other variants; select a matching board package or create one from boards/ with the correct target settings.
- Pair BLE key (DRIVER or CHARGING_MANAGER role)
- Wake vehicle, set charging amps/limit, start/stop charging
- Sensors: asleep/awake, locked/unlocked, user presence, charge port, BLE signal, IEC 61851
- Charging sensors: battery level, charge rate, energy added, time to full, charger phases
- Software update: status and version sensors, install and cancel buttons (polled every 5th infotainment cycle)
- Climate controls: per-seat climate, cabin overheat protection and temperature, steering heat mode, sunroof position cover
Seat entities/options are currently configured at build time. Cooling and Auto commands require support to have been observed in vehicle telemetry; unsupported options are not dynamically hidden as they are in the direct-BLE HA integration.
If you run the ESPHome add-on in Home Assistant:
- For a new, unflashed board, select Create configuration → Create new project and complete the name and Wi-Fi setup. For a board already running this project's firmware, use Adopt instead.
- Open the device's YAML editor. Keep the generated
esphome,api, andwifisections, but delete the generatedesp32:,logger:,ota:, andcaptive_portal:sections. The board package below supplies all of them (chip target, logging, encrypted OTA, captive portal); a leftover generatedota:carrying apassword:fails validation because the encryption key already authenticates the uploader. - Add these entries to the
secrets.yamlused by your ESPHome configuration. Find the vehicle MAC with an Android BLE scanner; iOS apps cannot reveal it.
tesla_ble_mac_address: "A0:B1:C2:D3:E4:F5"
tesla_vin: "5YJ30123456789ABC"- Add the vehicle and version settings to the top-level
substitutionssection:
substitutions:
# The left-hand names are fixed. Change only the secret names on the right.
# Set this to main, a branch, a release tag, or a commit SHA.
tesla_ble_ref: main
ble_mac_address: !secret tesla_ble_mac_address
tesla_vin: !secret tesla_vin- Add the board package under the top-level
packagessection. It provides the matchingesp32:target, logging, the captive portal, encrypted ESPHome OTA updates using the API encryption key (ESPHome 2026.9.0 or newer), and the custom components. Do not add separateesp32:,logger:,ota:,captive_portal:, orexternal_components:sections. For an M5Stack NanoC6:
packages:
yoziru.esphome-tesla-ble:
url: https://github.com/yoziru/esphome-tesla-ble.git
ref: ${tesla_ble_ref}
files: [tesla-ble-m5stack-nanoc6.dashboard.yml]- Save and Install. Future firmware updates are applied from this same device card with Update.
Use tesla-ble-m5stack-atoms3.dashboard.yml for an AtomS3 or tesla-ble-esp32-generic.dashboard.yml for a generic ESP32.
Wrong board? If the upload reaches 100% but ends with Finishing update failed, the board file doesn't match your chip (e.g. NanoC6 firmware on an AtomS3). Switch files: to the .dashboard.yml for your hardware and install again.
Set tesla_ble_ref once to test a branch, tag, or commit SHA. It selects both the YAML package and the custom C++ components. Local CLI builds need no override because they use the current checkout's components/ directory.
The first flash needs the generated name, Wi-Fi, and API settings plus the two vehicle secrets above. See Finding the BLE MAC if you do not have the MAC yet.
No pre-made file for your board? Point files: at packages/dashboard.yml and supply the chip settings yourself (see the sketch).
substitutions:
tesla_ble_ref: main
board: esp32-c6-devkitm-1
variant: esp32c6
flash_size: 4MB
ble_mac_address: !secret tesla_ble_mac_address
tesla_vin: !secret tesla_vin
packages:
yoziru.esphome-tesla-ble:
url: https://github.com/yoziru/esphome-tesla-ble.git
ref: ${tesla_ble_ref}
files: [packages/dashboard.yml]
esp32:
framework:
sdkconfig_options:
CONFIG_OPENTHREAD_ENABLED: nboard:/variant:/flash_size: must match the physical chip. Copy the full esp32: framework block from the closest file in boards/ (see boards/esp32-generic.yml for the minimal shape).
For example, an adopted NanoC6 with the YAML generated by current ESPHome Device Builder should look like this. Preserve the generated name, friendly name, API key, and Wi-Fi secrets from your device.
substitutions:
name: tesla-ble-5b1980
friendly_name: Tesla BLE 5b1980
tesla_ble_ref: main
charging_amps_max: "32"
ble_mac_address: !secret tesla_ble_mac_address
tesla_vin: !secret tesla_vin
packages:
yoziru.esphome-tesla-ble:
url: https://github.com/yoziru/esphome-tesla-ble.git
ref: ${tesla_ble_ref}
files: [tesla-ble-m5stack-nanoc6.dashboard.yml]
esphome:
name: ${name}
name_add_mac_suffix: false
friendly_name: ${friendly_name}
api:
encryption:
key: !secret api_encryption_key
wifi:
ssid: !secret wifi_ssid
password: !secret wifi_password
ap:
password: !secret wifi_hotspot_passwordSecret names are local to each user's dashboard. If yours differ, keep ble_mac_address and tesla_vin on the left and change only the names after !secret.
For multiple vehicles, create one ESPHome device per vehicle and map each device's substitutions to distinct secrets. For example, the second device can use ble_mac_address: !secret model_y_ble_mac_address and tesla_vin: !secret model_y_tesla_vin; the package and board configuration stay the same.
Or copy tesla-ble.example.yml as a starting point for custom configs.
Requires Python 3.10+, uv, and GNU Make.
cp secrets.yaml.example secrets.yaml # edit with your details
make validate-config BOARD=m5stack-nanoc6 # check config first
make compile BOARD=m5stack-nanoc6 # build firmware
make upload BOARD=m5stack-nanoc6 # flash via USB
make logs # view logsFor OTA updates after initial USB flash:
make discover # find your device on the network
make upload # OTA flash (uses saved suffix)Available boards: m5stack-nanoc6, m5stack-atoms3, esp32-generic.
All make commands accept BOARD=<board> to select the target hardware.
The role determines what the paired BLE key can do (set during pairing):
- DRIVER (default) — full access: lock/unlock, frunk/trunk, windows, honk, climate, charging
- CHARGING_MANAGER — charging only: start/stop, set amps, set limit, open charge port, basic info
The Tesla backend enforces these restrictions. Change requires re-pairing.
tesla_ble_vehicle:
vcsec_poll_interval: 10 # Status updates (always safe, low power)
infotainment_poll_interval_awake: 30 # Detailed data when idle
infotainment_poll_interval_active: 10 # Detailed data when charging/sentry mode
infotainment_sleep_timeout: 660 # Idle seconds before letting the car sleep (default 11 min)The system only polls infotainment data during an 11-minute wake window, then lets the car sleep. Active charging and sentry mode keep it awake for continuous updates. A car that is merely left unlocked, or that reports user presence because a phone is in range, is treated as idle — the integration backs off and lets it sleep regardless. VCSEC status polling is low-power and does not affect vehicle sleep.
Successful controls refresh the affected state after 1.5 seconds. Repeated controls coalesce that refresh; it does not wake a sleeping car and is cancelled on disconnect.
Your vehicle constantly advertises via BLE with a name derived from its VIN (format: S + 16 hex chars + C). This advertisement comes from VCSEC (vehicle security controller) which is always powered — no need to wake the car. You need the MAC address of that advertisement to configure the ESP32.
The tesla_ble_listener component is included in the firmware but disabled by default. You enable it temporarily, find the MAC, then disable it.
In a local checkout of this repo:
- Uncomment
listener: !include listener.ymlinpackages/base.yml - Add
tesla_vintosecrets.yaml - Build, flash, and watch logs:
make compile BOARD=m5stack-nanoc6 && make upload BOARD=m5stack-nanoc6 make logs - Note the MAC from the log output
- Re-comment the line in
packages/base.ymland runmake clean - Add
ble_mac_addresstosecrets.yaml, rebuild, and reflash
Use an Android BLE scanner app such as nRF Connect, scan nearby devices, and look for one with an 18-character name starting with S and ending with C (e.g., S1a87a5a75f3df858C). Record its MAC address. iOS scanner apps cannot display Bluetooth MAC addresses because Apple blocks that information; use an Android device or the CLI listener instead.
-
Sit in your car with the ESP32 powered and within BLE range
-
In Home Assistant: Settings → Devices & Services → ESPHome → your device → "Pair BLE key"
-
Tap your NFC key card to the center console immediately
-
A prompt appears on the car's screen — tap confirm
-
Verify: go to Controls → Locks in the car — you'll see a new key named "Unknown device"
-
[Optional] Rename it to "ESPHome BLE"
No popup? Press "Pair BLE key" and tap your card again. Make sure BLE MAC and VIN are correct.
Settings → Devices & Services → Add Integration → ESPHome. Enter the device's IP (find it in your router or from ESPHome Device Builder) and your API encryption key.
| Command | What it does |
|---|---|
make validate-config BOARD=<board> |
Check YAML without building |
make compile BOARD=<board> |
Build firmware |
make upload BOARD=<board> |
Flash via USB (add HOST_SUFFIX for OTA) |
make logs |
View live device logs |
make discover |
Find ESPHome devices on your network, saves suffix for OTA |
make clean |
Delete build artifacts (do this when changing config) |
make help |
Show all commands |
| Symptom | Likely cause |
|---|---|
| "Found Tesla vehicle" never appears | The listener isn't enabled. For CLI: uncomment listener: !include listener.yml in packages/base.yml. For Device Builder, use an Android BLE scanner before creating the vehicle package. VCSEC always advertises — no need to wake the car. |
| Pairing fails with HMAC error | BLE MAC or VIN is wrong. Verify both in secrets.yaml. |
| Car stays awake | Active charging or sentry mode keep it awake. Otherwise the integration backs off after infotainment_sleep_timeout of idle time and lets the car sleep — watch the log for Polling Infotainment (sleeping - NO_WAKE_SKIP). |
Unknown on boot |
Normal for some sensors — VCSEC corrects within ~10s. |
| Compile errors | Board mismatch? Run make clean, then make compile again. |
uv: command not found |
Install uv |