u/Cold_Arm3819

▲ 0 r/esp32

Six ESP32-S3 WiFi bugs that I think are one bug. Here's the list and the fix I'm planning, tell me where I'm wrong.

ESP32-S3, IDF 5.4. It's a desk terminal I built: WiFi STA, a WebSocket to my PC, another to a relay. Disclosure, it's headed toward being a product, but there's nothing to sell and no link, I just want the radio to stop embarrassing me.

I've been filing WiFi bugs against this thing for months as if they were unrelated. Last week I lined them up and I don't think they are.

1. It dies overnight. On a plain USB charger, usb_serial_jtag_is_connected() sees no host, so my "it's plugged in, stay awake" exemption never fires. Ten minutes after the last interaction, standby calls esp_wifi_disconnect() then esp_wifi_stop(). After that a duty-cycle poll opens a 2500 ms window to get back on. My socket layer retries on a 3000 ms timer. 3000 > 2500, so the window shuts before the retry lands, every time, forever. One night's logs: ~660 polls, zero successes. Dead 22:24 to 09:30. Touch it in the morning and it's back in a second.

2. It can't survive booting while the router is down. Every saved network gets 5 tries at boot. If they all fail I set a fail bit and stop. Forever-retry and roam only arm once s_wifi_ever_connected is true. So "AP wasn't ready at boot" means offline until a human power-cycles it.

3. It won't leave a weak mesh node. Three nodes, one SSID. scan_method on defaults, so FAST_SCAN takes the first match instead of the strongest. Reconnect by SSID, never pinning BSSID. Roam only fires on a disconnect event. Result: parked at -80 with a -45 node in the same room.

4. Roaming is directionally asymmetric. Coming back to the office it reconnects. Coming home it doesn't. Same two saved networks, same code path. I still don't have a clean repro and that bothers me more than the bug.

5. Half-open sockets. Keepalive send failures weren't counted as failures. A socket could be dead for 75 seconds while the UI just greyed out instead of reconnecting.

6. WiFi buffers are tiny. STATIC_RX 4, DYNAMIC_RX 8, DYNAMIC_TX 8, AMPDU off. I cut those to claw back internal RAM back when BT was still in the build. BT has been gone for months. The numbers stayed.

What they have in common. Every one of these fires a recovery attempt exactly once and then nothing checks whether it worked. The retry timer, the roam trigger, the standby poll, the keepalive counter: all ignition, no supervisor. When it works I never notice. When it doesn't there's no second attempt and no log line, so the device sits there looking perfectly healthy and is simply not on the network. #1 lived in my house for weeks disguised as "wifi is bad in that room."

What I'm planning:

  • One connection manager task that owns the radio, with explicit states. Everything else asks it for connectivity instead of calling esp_wifi_connect() itself. At least four places currently do.
  • Arm forever-retry on "credentials exist", not "has connected at least once".
  • Standby stops tearing down WiFi. WIFI_PS_MAX_MODEM with DTIM, keep the association. Have not measured the power cost yet.
  • esp_wifi_set_rssi_threshold() and treat WIFI_EVENT_STA_BSS_RSSI_LOW as a roam trigger, so "associated but bad" becomes a state I can act on instead of a blind spot.
  • ALL_CHANNEL_SCAN with CONNECT_AP_BY_SIGNAL, and pin the BSSID.
  • Put a boot-id in the frames I send upstream. A good chunk of my earlier log analysis was wrong because I couldn't distinguish a reboot from a reconnect.

Where I'd like to be told I'm wrong:

  • Anyone regret MAX_MODEM plus DTIM on an S3? Wake latency is what I'm nervous about, this thing is supposed to be always-listening.
  • What WiFi buffer numbers have people actually landed on for sustained WebSocket traffic on an S3 with esp-sr resident? I have very little internal RAM to spend and I'd rather steal someone's tested numbers than binary-search my own.
  • Is one connection-manager task the right shape, or does it just relocate the problem into a bottleneck?

Mostly I want to know whether "one-shot ignition with no supervisor" is a named mistake that everyone else already learned to avoid, or whether we all write it once and pay for it.

reddit.com
u/Cold_Arm3819 — 2 days ago
▲ 370 r/cyberDeck

Three buttons wasn't enough, so I gave it the whole session

Posted a version of this over on r/esp32 and a couple of people said it belonged here. Fair warning up front: it is not strictly a cyberdeck. It doesn't go in a backpack and there's no HMD. But it came from the same impulse, so I'll let you decide.

The first version was three buttons and a small screen. It could tell me that something had happened, a task finished or something needed me, but not what. Which meant every time it lit up I still had to walk back to the desk to find out whether it mattered. It was a doorbell, not a window.

What I actually wanted was dumber and more specific than that. I wanted to lie on the couch and still be following the project.

So this one mirrors the whole thing. Every running session down the left, and the full conversation of whichever one I'm looking at on the right. Not a summary, the actual output, scrollable. When it stops and asks for permission I can approve from here. When it asks a multiple choice question I pick with the knob.

The upshot is that I can now get through most of a working session without sitting at the machine. Not all of it, obviously. I'm not writing code on a 3.5" screen. But the reading, approving and nudging-it-along part turns out to be most of the hours, and that now happens from wherever I am.

Guts: ESP32-S3, 3.5" 480x320 , mic array, rotary encoder, three mechanical switches. The enclosure is printed and then sanded and primed to death, and that part took longer than the firmware did.

(Same disclosure I gave on r/esp32: I am turning this into a product eventually. No link, nothing to sell today.)

Happy to answer anything. Still working out what actually belongs on a screen this size.

u/Cold_Arm3819 — 3 days ago
▲ 30 r/esp32

I built an ESP32-S3 desk terminal (3.5in, mic array) — the memory and LVGL gotchas that nearly killed it

Short version: it's a small desk terminal built around an ESP32-S3 that mirrors what my coding agent is doing on my PC . It talks over WebSocket to a small bridge running on my machine. I've had it on my desk for a few months now.

Hardware:

- ESP32-S3
- 3.5" 480x320, ST7796 over SPI, driven through esp_lcd + LVGL 9.2
- ES7210 mic array, ES8311 codec, NS4168 amp
- Rotary encoder + 3 mechanical key switches

The parts that actually cost me time:

  1. Internal RAM. This was the whole war. Display buffers, LVGL, WiFi, WebSocket and the audio pipeline all want internal SRAM and PSRAM doesn't help for most of it. At the worst point I had 4.7 KB free internal heap during a voice session. It sits around 27 KB in the same state now. What actually moved the needle: task stacks into PSRAM where allowed, WiFi TX buffers from 16 down to 8, killing BT in sdkconfig.defaults (it quietly claims ~30 KB even when you never call it), and tracking down a heap_caps alloc/free mismatch that leaked on every reconnect.

  2. PSRAM stacks and flash I/O do not mix. Follow-on from the above: I moved a pile of task stacks into PSRAM, and one of them was the task that reads audio assets off SPIFFS. Instant crash, every time, very confusing. If a task touches flash or SPIFFS, its stack has to stay in internal RAM. That one cost me a day.

  3. Half-drawn characters in LVGL. Text in my cards would occasionally render with a glyph sliced down the middle. Root cause was two render paths disagreeing: the full-frame path skips a glyph entirely when it doesn't fit, the partial-redraw path happily clips it mid-glyph. Same string, different result depending on which path ran. Fixed by truncating to whole characters myself before handing the string to LVGL instead of trusting it to clip.

  4. Regenerating sdkconfig loses things you can't see. I deleted and regenerated sdkconfig once and silently lost two settings I had only ever touched in menuconfig (mbedtls external mem, sys_evt stack size). The result was a boot loop that took a while to trace back to that. Anything load-bearing lives in sdkconfig.defaults now.

Firmware is being open sourced in stages. Happy to go deeper on any of this.

Fair warning: I am turning this into a product eventually, so weigh my enthusiasm accordingly. Nothing to sell yet and no link.

Anyone else fighting the internal-RAM-versus-everything war on the S3? Curious what people cut first.

u/Cold_Arm3819 — 4 days ago