Maker, meme-r, and unabashed geek, Joe has been writing about technology since starting his career in 2018 at KnowTechie. He's covered everything from Apple to apps and crowdfunding and loves getting to the bottom of complicated topics. In that time, he's also written for SlashGear and numerous corporate clients before finding his home at XDA in the spring of 2023.
He was the kid who took apart every toy to see how it worked, even if it didn't exactly go back together afterward. That's given him a solid background for explaining how complex systems work together, and he promises he's gotten better at the putting things back together stage since then.
There's nothing I like more than turning cheap ESP32 boards into Bluetooth proxies for Home Assistant, and I'm always tempted to give them a second job. Not all ESP32 boards are the same, and my XIAO ESP32-S3 Sense has a camera expansion board, so I naturally wanted it to stream into Home Assistant while relaying Bluetooth sensors at the same time. So I loaded it up and pushed it until it broke.
I say crashing, but that's not quite what happened. The board didn't trigger a reboot, or a crash log, or even show errors. The board just went quiet while Home Assistant kept showing me numbers that looked perfectly healthy. Memory was the culprit, not my Wi-Fi.
Both radios draw from the same small pool of internal RAM
Every ESP32 has a single RF path, and Wi-Fi and Bluetooth take turns using it. Espressif's coexistence documentation calls it time-division multiplexing, a fancy way of saying the radios split the airtime. When both are connected, each gets roughly half. That can make things sluggish, but it rarely kills a board on its own.
You can actually watch this happen in ESPHome's logs. Every time my proxy connected to a nearby Bluetooth device, it logged Setting coexistence to Bluetooth to make connection, then handed the radio back with Setting coexistence preference to balanced a couple of seconds later. Your Wi-Fi traffic waits its turn while that happens, which is why ESPHome warns that aggressive scan settings can destabilize Wi-Fi on wireless proxies.
The real fight is over RAM. An ESP32-S3 has about 512KB of internal SRAM, and the Wi-Fi stack and Bluetooth controller both allocate their buffers from it at runtime. ESPHome's own BLE component page warns that the Bluetooth stack "consumes a significant amount of RAM" and that crashes are likely if you add too many other components. It specifically calls out voice and audio components as the worst offenders.
And it's not just the total that matters. Memory gets chopped into pieces as things are allocated and freed, and a request fails when no single free block is big enough, even if there's plenty free in total. ESPHome's debug component reports this as fragmentation, and says anything over about 50% may cause allocation failures. Keep that number in mind, because it can bite you.
I pushed my ESP32-S3 until it stopped talking to Home Assistant
Adding Bluetooth cut my usable memory in half
I built the test in ESPHome Device Builder on a XIAO ESP32-S3 Sense with 8MB of PSRAM, with the camera streaming 800x600 video into Home Assistant the whole time. Each config added a couple of buttons that grab internal RAM in 16KB and 4KB chunks and never give it back. Pressing them until the board struggles tells you how much headroom is really left. I also added the debug component's heap sensors so I could watch it happen.
debug:
update_interval: 5ssensor:
- platform: debug
free:
name: "Heap Free"
block:
name: "Heap Max Block"
fragmentation:
name: "Heap Fragmentation"
With just Wi-Fi and the camera, the board was shockingly resilient. I filled 200KB of extra RAM, free memory dipped to 320 bytes at one point, and the camera kept streaming smoothly for over 20 minutes. The frame buffers live in PSRAM, so the camera barely touched internal memory. Then I enabled the Bluetooth proxy, and everything changed.
|
Config |
Internal free while streaming |
Extra RAM before trouble |
|---|---|---|
|
Wi-Fi and camera |
211KB |
200KB, never failed |
|
Plus Bluetooth proxy |
101KB to 121KB |
96KB to 100KB |
|
Plus web server |
96KB to 112KB |
80KB |
The Bluetooth proxy ate around 90KB of internal RAM before I'd done anything. The remaining free memory was more fragmented, too. Fragmentation hit 86% with Bluetooth running, against 73% without it at a much higher load. Adding ESPHome's web server took another 16KB or so on top of that.
It didn't crash, which was the problem
When the Bluetooth config hit its limit, I expected a reboot and a stack trace. Instead, ESPHome started logging Buffer full, ping queued, the camera dropped from 260 frames a minute to 14, and the board eventually cut off both Home Assistant and the log viewer as unresponsive. Over USB, I could see the board was still running and still connected to Wi-Fi with a strong signal. It just didn't have the memory left to actually talk to anyone.
Home Assistant didn't notice right away, either. In my first run, it kept showing the last sensor values for about two minutes before marking everything unavailable. That's two minutes of stale data presented as live, which is exactly how Home Assistant automations end up firing on dead sensor data.
The board eventually recovered on its own. ESPHome's API has a reboot timeout that restarts a device if nothing has connected for 15 minutes, and mine rebooted in about a minute. But a Bluetooth proxy that silently disappears for a quarter of an hour isn't much of a proxy.
The fix is to give each ESP32 fewer jobs
The settings everyone recommends barely move the needle on my S3
ESPHome's Bluetooth proxy docs list a few memory tweaks, and I tried them. Dropping from three connection slots to two and switching to passive scanning gave me 100KB of headroom, compared to 96KB to 100KB at the default. That's within measurement error, so no meaningful improvement here. Each slot only costs about 1KB, so there's not much to claw back.
The bigger surprise was use_psram. ESPHome's BLE docs say it moves the Bluetooth stack's buffers to PSRAM and frees roughly 40KB of internal RAM, which sounded like the perfect fix for a board with 8MB of PSRAM. I turned it on, and the free PSRAM figure didn't budge by a single byte. Digging into ESPHome’s source, the setting that actually sends Bluetooth allocations to PSRAM only exists on the original ESP32, which supports both classic Bluetooth and Bluetooth Low Energy. Newer chips like the S3 and C3 dropped classic Bluetooth, and that setting went with it.
On my S3, the option only switches Bluetooth's internal tables to dynamic allocation, and those still come out of internal RAM. The boot log even prints PSRAM BLE allocation: enabled, which is technically a line of text and practically a lie.
ESP32 boards are cheap, use one for each specialist job
If your ESP32 keeps vanishing from Home Assistant while running a Bluetooth proxy, add debug sensors to the build and drop extras like a web server. That's a good start, but using one board for Bluetooth proxying and another for other tasks is the surefire way to go. Sure, a second ESP32 is one more thing to power, but they're cheap, and a proxy that never drops off the network is worth far more than the USB port it takes. I'm removing Bluetooth from my camera board for good, because it's not worth the hassle.





