Files
Ben Meadors dc8d2a11e4 feat(usbnet): USB-Ethernet (CDC-NCM) gadget + phone API for native-USB ESP32
Presents the node to a USB host as a standard USB Ethernet adaptor and serves
the existing TCP phone API (port 4403) over it, so a USB-C iPad/iPhone can reach
a node over a wire - including in airplane mode, since the link is not a radio.
USB *serial* is permanently closed on iPadOS (Apple's own dexts win IOKit
matching and expose no user client), so USB-Ethernet is the only wired route.
Apple DTS recommends exactly this shape for an ESP32-S3 (developer forums
thread 772812); network adaptors are generally not MFi-licensed.

Opt-in per variant via HAS_USB_NET; every file is self-gated so non-opted-in
environments are byte-identical and no build_src_filter changes are needed.
Pilot env meshnology_w12_usbnet at board_level = extra (absent from the PR and
release CI matrices).

STATUS: the transport works end to end and is hardware-verified. iPadOS 26
enumerates the gadget and joins the network. On macOS the host gets a DHCP lease
of 192.168.7.2 with NO router option and NO DNS option, the default route is not
hijacked, ping is ~1.3ms, and 4403 accepts while 4404/80 correctly refuse.
The phone API over that transport does NOT work yet: the node reboots when a
client opens a session on 4403. Reproducible from macOS with no Meshtastic app
involved, so it is firmware, and it needs a real TCP session (connecting to an
address with nothing on it does not trigger it). Two theories are now ruled out
by measurement: thread-table exhaustion (boot logs 20/40 threads used) and
HWCDC console blocking (the screen keeps running). Coredump-to-flash is wired
into the env for the next session but did NOT take effect in this build -
HybridCompile reused cached libs and CONFIG_ESP_COREDUMP_ENABLE_TO_FLASH is
absent from the generated sdkconfig; force an IDF rebuild before trusting it.

Design notes, all load-bearing:
- L3 policy follows Apple guidance (Quinn, developer forums 779796): vend no
  gateway and no DNS, so iOS cannot elect the link as a default route, never
  runs its captive probe against it, and never marks it dead. gw is 0.0.0.0,
  the router offer flag is cleared, and CONFIG_LWIP_DHCPS_ADD_DNS is off -
  without that last one dhcpserver.c vends the node's own address as a DNS
  server that answers nothing. Deliberately NOT warthog's NAT gateway model.
- Bring-up order is the correctness argument: netif + DHCP + API listener all
  come up in setup(), and only then does the USB device start and the NCM link
  get raised. iOS runs DHCP exactly once on link-up and never retries.
- tud_network_default_link_state_cb() is overridden to start the link DOWN.
- The USB task is pinned to core 0 via TINYUSB_TASK_CUSTOM(). esp_tinyusb 2.x
  moved task config out of Kconfig, so CONFIG_TINYUSB_TASK_STACK_SIZE and
  CONFIG_TINYUSB_TASK_AFFINITY_CPU0 do not exist and are silently ignored; the
  default is core 1 at priority 5, the same core as the Arduino loop task at
  priority 1.
- usbNetTransmit refuses early and waits only briefly: with
  CONFIG_LWIP_TCPIP_CORE_LOCKING it runs inline on the caller, and
  tinyusb_net_send_sync enqueues with an infinite wait. Dropping a frame is
  free because TCP retransmits; blocking the loop task is not.
- TinyUSB must resolve to >= 0.21.0 (pinned explicitly): PR #3630 is what makes
  NCM work on iOS/iPadOS 26, and esp_tinyusb alone only requires >= 0.17.0~2.
- USBNetPolicy is free of Arduino/IDF deps so the DHCP option policy and MAC
  derivation can be covered by the native test suite (tests not yet written).

Known rough edges for the next session:
- Requires native USB on the connector (ESP32-S2/S3/P4). The C3/C6/H2 have
  USB-Serial-JTAG only and cannot do this. Heltec V3 is disqualified - its
  USB-C is a UART bridge.
- The gadget owns the USB pads, so the serial console dies once it starts and
  esptool has no RTS line to auto-reset with. USB_NET_START_DELAY_MS is 30s to
  keep a reflash window; BOOT+RST parks the chip in ROM download mode with no
  time limit.
- extra_scripts/usbnet_link.py adds the managed-component archives to LIBPATH;
  HybridCompile builds them but never adds them to pioarduino-build.py's LIBS.
- bin/restore-idf-component-yml.sh restores the shared framework
  idf_component.yml after a custom_component_add build. The platform does
  auto-restore it, so this is belt and braces - do not run it before building
  the usbnet env or the component is stripped from the dependency list.
2026-08-06 15:24:10 -05:00
..
2026-07-01 19:01:27 -05:00
2026-07-01 19:01:27 -05:00
2026-07-01 19:01:27 -05:00

extra_scripts

This directory contains special scripts that are used to modify the platformio environment in rare cases.