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.
28 lines
1.2 KiB
Python
28 lines
1.2 KiB
Python
"""Link the esp_tinyusb managed components into a USB-Ethernet gadget build.
|
|
|
|
`custom_component_add = espressif/esp_tinyusb` makes pioarduino build
|
|
libespressif__esp_tinyusb.a and libespressif__tinyusb.a, but it never adds them
|
|
to pioarduino-build.py's LIBS, so the application links without them and fails
|
|
on tinyusb_driver_install / tinyusb_net_init.
|
|
|
|
The archives land in one of two places depending on whether the IDF libs were
|
|
rebuilt for this environment (project build dir) or reused from the shared
|
|
framework package (already on LIBPATH), so add the project location here and let
|
|
`-lespressif__*` in the env's build_flags resolve from whichever exists.
|
|
|
|
Doing this in a script rather than as `-L` build_flags is deliberate: PlatformIO
|
|
routes `-L` into LIBPATH but does not resolve a relative path from the project
|
|
root, so the entry silently misses. $BUILD_DIR is unambiguous.
|
|
"""
|
|
|
|
import os
|
|
|
|
Import("env") # noqa: F821
|
|
|
|
build_dir = env.subst("$BUILD_DIR") # noqa: F821
|
|
idf_dir = os.path.join(build_dir, "esp-idf")
|
|
|
|
for component in ("espressif__esp_tinyusb", "espressif__tinyusb"):
|
|
path = os.path.join(idf_dir, component)
|
|
env.Append(LIBPATH=[path]) # noqa: F821
|