diff --git a/build.sh b/build.sh index 2b5539a2..894103bd 100755 --- a/build.sh +++ b/build.sh @@ -2736,11 +2736,7 @@ requires_full_usb_packet_logging() { # Full Companion and the combined USB+WiFi infrastructure artifact always # carry the logger behind runtime controls. Other Full profiles retain the # ordinary explicit logging-off/disable-debug behavior. - # T1000-E Full has a single, stability-critical USB CDC endpoint. It keeps - # that endpoint for Companion data and the terminal, not optional packet - # logging, so its packaged contract must not promise a logger. - [ "$env_name" != "t1000e_companion_radio_full" ] \ - && is_companion_radio_full_target "$env_name" && return 0 + is_companion_radio_full_target "$env_name" && return 0 [ "$ESP32_FULL_BUILD" = "1" ] || return 1 is_companion_build "$env_name" && return 1 [ "${PACKET_LOGGING_OVERRIDE,,}" = "on" ] && return 0 @@ -3291,9 +3287,7 @@ declare_build_capability_contract() { record_build_expectation "companion.usb" "+++MESHCORE-TERM-START" record_build_expectation "companion.bluetooth" \ "Companion: starting Bluetooth" - if [ "$env_name" != "t1000e_companion_radio_full" ]; then - record_build_expectation "companion.usb_logging" "get usb.logging" - fi + record_build_expectation "companion.usb_logging" "get usb.logging" record_build_expectation "companion.usb_mota_source" "ota folder on" record_build_expectation "companion.mota_sender" \ "_ZN4mesh3ota16SerialMotaSource4read" @@ -3852,9 +3846,9 @@ apply_companion_radio_full_profile() { # an exclusive host-folder mode with its existing `ota folder on` preamble. # CDC 1 is a write-only plaintext packet/debug logging stream; BLE remains # an independent Companion link. ESP32 intentionally does not use this - # dual-CDC capability. The T1000-E's nRF52840 USB hardware resets when - # its persisted logging preference asks TinyUSB to activate CDC 1, so it - # intentionally keeps logging on the primary USB interface instead. + # dual-CDC capability. The T1000-E has one stable CDC endpoint only: its + # logging preference claims that primary port as an ASCII terminal plus + # logs, and therefore never creates CDC 1 or mixes logs with framed USB. append_platformio_build_unflags "-UOTA_FOLDER_SERIAL" # The nRF52 core's production-safe Bluefruit bootstrap must retain its # early SoftDevice diagnostics. Without CFG_DEBUG, a few nRF52840 USB @@ -3864,15 +3858,11 @@ apply_companion_radio_full_profile() { export PLATFORMIO_BUILD_FLAGS="${PLATFORMIO_BUILD_FLAGS} -DOTA_FOLDER_SERIAL=1 -DCOMPANION_FEATURE_USB_MOTA_SOURCE=1 -DCOMPANION_FEATURE_BLE_MOTA_SOURCE=1 -DMESH_DEBUG=1 -DMESH_PACKET_LOGGING=1 -DCFG_DEBUG=1 -DRECOVERABLE_EXTERNAL_RADIO=1" if [ "$env_name" = "t1000e_companion_radio_full" ]; then # The T1000-E's primary CDC transport is the only USB interface. Its - # optional packet/debug logger can make the device reset while a host is - # enumerating, so keep Full Companion USB strictly data/terminal-only. - # This also ignores a stale persisted `usb.logging on` preference from - # an older dual-CDC image, allowing the device to recover automatically. - export PLATFORMIO_BUILD_FLAGS="${PLATFORMIO_BUILD_FLAGS} -DMESH_USB_LOGGING_DISABLED=1" - # MESH_USB_LOGGING_DISABLED still silences MeshCore's optional packet - # logger on the shared CDC endpoint. The shared nRF52 Full profile above - # retains the validated BLE bootstrap and radio recovery behavior. - append_platformio_build_unflags "-UMESH_DEBUG -UMESH_PACKET_LOGGING" + # `usb.logging on` selects an input-capable ASCII logging terminal on + # CDC 0. `usb.logging off` restores automatic framed-Companion detection. + # Do not instantiate or re-enumerate a second CDC interface on this + # board: its native USB/SoftDevice handoff is sensitive to that change. + : else export PLATFORMIO_BUILD_FLAGS="${PLATFORMIO_BUILD_FLAGS} -DCOMPANION_FEATURE_DEDICATED_USB_LOGGING=1 -DCFG_TUD_CDC=2 -DMESH_DUAL_CDC_LOGGING=1" fi diff --git a/examples/companion_radio/main.cpp b/examples/companion_radio/main.cpp index b9ddb161..d7ecbc2a 100644 --- a/examples/companion_radio/main.cpp +++ b/examples/companion_radio/main.cpp @@ -976,6 +976,19 @@ static void beginUsbDefaultSession() { static void serviceUsbTerminal() { #if COMPANION_FEATURE_USB_MOTA_SOURCE if (usb_mota_mode) { +#if MESH_USB_LOGGING_AVAILABLE + // A single-TTY logger and framed USB mOTA cannot share the primary USB + // stream. A remote logging change can arrive while mOTA owns USB, so stop + // the transfer before its next binary exchange and return to ASCII. + if (!mesh::hasDedicatedUsbLoggingPort() + && mesh::isUsbLoggingEnabled()) { + leaveUsbMotaMode(false); + enterUsbLoggingTerminalMode(); + usbTerminalOutput().print( + "\r\nUSB mOTA stopped: USB logging owns this port\r\n> "); + return; + } +#endif serviceUsbMota(); return; } @@ -1093,6 +1106,15 @@ static void serviceUsbTerminal() { // instead of letting the ordinary terminal command handler leave the // stream in line-oriented mode. if (strcmp(usb_terminal_line, USB_MOTA_START_TOKEN) == 0) { +#if MESH_USB_LOGGING_AVAILABLE + if (!mesh::hasDedicatedUsbLoggingPort() + && mesh::isUsbLoggingEnabled()) { + clearUsbTerminalLine(); + usbTerminalOutput().print( + "\r\nERROR: set usb.logging off before USB mOTA\r\n> "); + return; + } +#endif leaveUsbTerminalMode(false); enterUsbMotaMode(mesh::UsbMotaEntryOrigin::ASCII); return; diff --git a/test/test_build_profiles.sh b/test/test_build_profiles.sh index bdbc3692..38a82e00 100644 --- a/test/test_build_profiles.sh +++ b/test/test_build_profiles.sh @@ -598,8 +598,12 @@ for full_env in "${SUPPORTED_PIO_ENVS[@]}"; do || fail "$full_env must keep a single USB CDC interface" [[ "$PLATFORMIO_BUILD_FLAGS" != *"MESH_DUAL_CDC_LOGGING=1"* ]] \ || fail "$full_env must keep USB logging on its primary interface" - [[ "$PLATFORMIO_BUILD_FLAGS" == *"MESH_USB_LOGGING_DISABLED=1"* ]] \ - || fail "$full_env must disable its unstable primary USB logger" + [[ "$PLATFORMIO_BUILD_FLAGS" != *"MESH_USB_LOGGING_DISABLED=1"* ]] \ + || fail "$full_env must retain its single-port USB logging terminal" + [[ "$PLATFORMIO_BUILD_FLAGS" == *"MESH_DEBUG=1"* ]] \ + || fail "$full_env must retain USB diagnostics on its logging terminal" + [[ "$PLATFORMIO_BUILD_FLAGS" == *"MESH_PACKET_LOGGING=1"* ]] \ + || fail "$full_env must retain packet logging on its logging terminal" [[ "$PLATFORMIO_BUILD_FLAGS" != *"COMPANION_BLE_PRPH_MTU="* ]] \ || fail "$full_env must retain the normal Full-Companion BLE bandwidth" [[ "$PLATFORMIO_BUILD_FLAGS" != *"COMPANION_BLUETOOTH_BOOT_DELAY_MS="* ]] \ @@ -794,7 +798,7 @@ done for logging_target in Station_G2_companion_radio_full RAK_4631_companion_radio_full; do verify_full_logging_contract "$logging_target" 0 off 1 yes no done -verify_full_logging_contract t1000e_companion_radio_full 0 off 1 no no +verify_full_logging_contract t1000e_companion_radio_full 0 off 1 yes no # Synthetic inventory: ESP32 and nRF52 repeater/room-server targets with the # same lean OTA policy. The sensor deliberately has no lean sibling. diff --git a/tools/mota/test_mota.py b/tools/mota/test_mota.py index 544dc802..7abd41d4 100644 --- a/tools/mota/test_mota.py +++ b/tools/mota/test_mota.py @@ -339,12 +339,12 @@ def test_canonical_bulk_matrix_omits_runtime_and_transport_aliases(): assert "-DCFG_TUD_CDC=2" in nrf52_full assert "-DMESH_DUAL_CDC_LOGGING=1" in nrf52_full # Every nRF52 Full image keeps the proven Bluefruit bootstrap and radio - # recovery profile. T1000-E alone disables its optional packet logger - # because its sole CDC endpoint is reserved for Companion data/terminal. + # recovery profile. T1000-E keeps its sole CDC endpoint for a mutually + # exclusive ASCII logging terminal rather than instantiating CDC 1. assert "-DCFG_DEBUG=1" in nrf52_full assert "-DRECOVERABLE_EXTERNAL_RADIO=1" in nrf52_full assert 'if [ "$env_name" = "t1000e_companion_radio_full" ]; then' in nrf52_full - assert "-DMESH_USB_LOGGING_DISABLED=1" in nrf52_full + assert "MESH_USB_LOGGING_DISABLED" not in nrf52_full esp32_full = full.split("return 0\n fi", 1)[1] assert "-DCFG_TUD_CDC=2" not in esp32_full assert "-DMESH_DUAL_CDC_LOGGING=1" not in esp32_full @@ -481,6 +481,12 @@ def test_single_tty_logging_off_restores_each_builds_default_mode(): "if (strcmp(usb_terminal_line, USB_TERMINAL_STOP_TOKEN) == 0) {", 1 )[1].split("}", 1)[0] assert "leaveUsbTerminalMode(true);" in terminal_stop + usb_mota_start = service.split( + "if (strcmp(usb_terminal_line, USB_MOTA_START_TOKEN) == 0) {", 1 + )[1].split("leaveUsbTerminalMode(false);", 1)[0] + assert "set usb.logging off before USB mOTA" in usb_mota_start + assert "!mesh::hasDedicatedUsbLoggingPort()" in usb_mota_start + assert "mesh::isUsbLoggingEnabled()" in usb_mota_start assert "if (!usb_logging_terminal_mode" in service assert "usb_binary_startup_probe.shouldStart(" in service