Skip to content

Fix build + HTTP + mDNS so the firmware actually serves out of the box#1

Merged
zhenquan-yao merged 3 commits into
m5stack:mainfrom
pmokbel:fix/upstream-bugs
May 28, 2026
Merged

Fix build + HTTP + mDNS so the firmware actually serves out of the box#1
zhenquan-yao merged 3 commits into
m5stack:mainfrom
pmokbel:fix/upstream-bugs

Conversation

@pmokbel
Copy link
Copy Markdown
Contributor

@pmokbel pmokbel commented May 27, 2026

Three small fixes that get the published source building and serving HTTP correctly out of the box. Each fix is its own commit with a detailed message; this PR is intended to be safe to merge as-is with no behavioral change beyond "the device works."

1. Enable TinyUSB suspend/resume Kconfigs (bf7d6ff)

main/hal/storage/hal_storage.cpp:151,156 uses TINYUSB_EVENT_SUSPENDED and TINYUSB_EVENT_RESUMED unconditionally. In espressif/esp_tinyusb >= 2.x those enum values are gated behind CONFIG_TINYUSB_SUSPEND_CALLBACK / CONFIG_TINYUSB_RESUME_CALLBACK. dependencies.lock doesn't pin esp_tinyusb, so the component manager resolves the latest and a fresh idf.py build from a clean clone fails with:

error: 'TINYUSB_EVENT_SUSPENDED' was not declared in this scope
error: 'TINYUSB_EVENT_RESUMED'   was not declared in this scope

Fix: enable both Kconfigs in sdkconfig.defaults.

2. Bump LWIP_MAX_SOCKETS so httpd_start can bind (e87a2cb)

apps/app_server/app_server.cpp:1418 sets hc.max_open_sockets = 13. ESP-IDF's httpd_start() validates this against LWIP_MAX_SOCKETS - 3 (three sockets reserved internally). The IDF default LWIP_MAX_SOCKETS=10 leaves only 7 available, so httpd_start() returns ESP_ERR_INVALID_ARG:

E (...) httpd: Config option max_open_sockets is too large
        (max allowed 7, 3 sockets used by HTTP server internally)
        Either decrease this or configure LWIP_MAX_SOCKETS to a larger value
E (...) app_server: httpd start fail

The HTTP server then never binds port 80. Devices flashed from a clean build of this repo accept Wi-Fi associations and DHCP, but nothing answers on 192.168.4.1 — captive portal and LAN UI are both unreachable. The error logs to UART (GPIO 5/4 per the current console config), so the symptom is invisible to anyone debugging over USB-CDC.

Fix: set CONFIG_LWIP_MAX_SOCKETS=16 so the configured 13 + 3 internal fits.

3. Initialize mDNS at boot instead of on first AP client (f61f77e)

mdns_init() / mdns_hostname_set() / mdns_instance_name_set() lived inside the WiFiEvent::AP_STA_CONNECTED event handler — i.e. mDNS was only initialized when a phone joined the device's softAP.

Consequences:

  • In STA-only operation (or any boot where no station ever joins the softAP), mDNS is never initialized and <device_name>.local doesn't resolve on the LAN.
  • The behavior is timing-dependent: as soon as any phone briefly joins the AP, mDNS starts working retroactively for STA too. This makes the bug intermittent and easy to miss in testing.

Fix: move the init to app_server_init, right after URI handlers are registered. The mdns component watches netif up/down events itself, so one init at boot covers both STA and AP cleanly. The existing rename path in h_wifi_config already re-calls mdns_hostname_set() so renaming still works.

Also adds an _http._tcp service advertisement so the device shows up in Finder's Network sidebar and dns-sd -B _http._tcp without needing to know the hostname.

The event handler is reduced to just logging the transition.


Test plan

Built clean against ESP-IDF v5.5.1 with idf.py build. Before the LWIP fix: httpd start fail at boot. After: papercolor server ready, 18 routes and http://192.168.4.1/ serves the captive portal. After the mDNS fix: papercolor.local resolves on STA without needing a phone to ever associate to the AP.

No behavioral change beyond "the documented behavior actually happens."

pmokbel added 3 commits May 27, 2026 10:08
main/hal/storage/hal_storage.cpp:151,156 use TINYUSB_EVENT_SUSPENDED and
TINYUSB_EVENT_RESUMED unconditionally:

    case TINYUSB_EVENT_SUSPENDED:  ...
    case TINYUSB_EVENT_RESUMED:    ...

In espressif/esp_tinyusb >= 2.x those enum values are gated by
CONFIG_TINYUSB_SUSPEND_CALLBACK and CONFIG_TINYUSB_RESUME_CALLBACK
(see managed_components/espressif__esp_tinyusb/include/tinyusb.h).

dependencies.lock does not pin esp_tinyusb, so the component manager
resolves to the latest version and a fresh `idf.py build` from a clean
clone fails with:

    error: 'TINYUSB_EVENT_SUSPENDED' was not declared in this scope
    error: 'TINYUSB_EVENT_RESUMED'   was not declared in this scope

Enable both Kconfigs in sdkconfig.defaults so the source matches the
component's API surface again.
apps/app_server/app_server.cpp:1418 sets:

    hc.max_open_sockets = 13;

ESP-IDF's httpd validates this against LWIP_MAX_SOCKETS minus 3 (three
sockets are reserved internally). With the IDF default LWIP_MAX_SOCKETS=10
only 7 sockets are available, so httpd_start() fails at boot:

    E (...) httpd: Config option max_open_sockets is too large
            (max allowed 7, 3 sockets used by HTTP server internally)
            Either decrease this or configure LWIP_MAX_SOCKETS to a larger value
    E (...) app_server: httpd start fail

The HTTP server then never binds port 80. Devices flashed from a clean
build of this repo present an open AP that accepts associations and hands
out DHCP leases, but nothing answers on 192.168.4.1 — the captive portal
and the entire web UI are unreachable. The same applies on STA / LAN.

The error is logged to UART (GPIO 5/4 per the current console config),
which makes the symptom invisible to anyone debugging over USB-CDC.

Set CONFIG_LWIP_MAX_SOCKETS=16 so the configured 13 + 3 internal fits.
…lient

The mdns_init() / mdns_hostname_set() / mdns_instance_name_set() calls
were inside the WiFiEvent::AP_STA_CONNECTED event handler — i.e. mDNS
was only initialized when a phone joined the device's softAP.

Consequences:

* In STA-only operation (or any boot where no station ever joins the
  softAP), mDNS is never initialized and <device_name>.local does not
  resolve on the LAN. Users get an IP from their router's DHCP but the
  documented papercolor.local hostname doesn't work.
* The behavior is timing-dependent: as soon as one phone briefly joins
  the AP, mDNS comes up retroactively and starts working on STA too. This
  makes the bug intermittent and easy to miss.

Move the init to app_server_init, right after the URI handlers are
registered. The mdns component watches netif up/down events itself, so a
single init at boot covers both STA and AP transitions cleanly. The
existing rename path in h_wifi_config already re-calls
mdns_hostname_set() when device_name changes, so renaming still works.

Also add an _http._tcp service advertisement so the device shows up in
Finder's Network sidebar and other Bonjour browsers without needing to
know the hostname in advance.

The event handler now only logs the AP_STA_CONNECTED transition.
pmokbel added a commit to pmokbel/M5PaperColor-album that referenced this pull request May 27, 2026
…stream

README is rewritten from the upstream's terse build-and-flash instructions
into a description of what this fork actually is: a single-purpose LAN
photo frame. Covers:

- The one-line pitch (upload photos via http://papercolor.local from any
  LAN browser, no app or cloud)
- Hardware target, build/flash, with the TinyUSB-MSC flashing caveat
  spelled out (esptool can only catch the device during the brief
  pre-MSC boot window)
- First-run onboarding via the open AP, and how to re-onboard later via
  long-press
- A 'differences from upstream' section split into 'bugs we fixed and
  pushed upstream' (PR m5stack#1 against m5stack/M5PaperColor-UserDemo) and
  'opinionated changes' (no cloud, STA-first, LAN-accessible upload,
  single-mode UI)
- Known caveats: open AP during onboarding, EzData .cpp still compiled
  in, factory-guide boot flow, console on UART

CHANGES is a complete file-by-file accounting of how the fork diverges
from m5stack/M5PaperColor-UserDemo@0b3d5ef, structured around the commit
topology so the upstream-PR-worthy fixes stay clearly separable from
the opinionated changes.

sdkconfig.defaults: small inline comment in the Console UART section
explaining the USB-Serial-JTAG / TinyUSB MSC interaction so anyone
reading the config understands why the flashing workflow needs
power-cycling.
@zhenquan-yao zhenquan-yao merged commit 1ff998e into m5stack:main May 28, 2026
1 check passed
@pmokbel pmokbel deleted the fix/upstream-bugs branch May 28, 2026 02:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants