mirror of
https://github.com/meshtastic/firmware.git
synced 2026-09-21 22:05:26 -04:00
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.