diff --git a/build.sh b/build.sh index e36c51c6..0443bb15 100755 --- a/build.sh +++ b/build.sh @@ -114,7 +114,7 @@ Commands: build-room-server-firmwares: Build all chat room server firmwares for all build targets. build-sensor-firmwares: Build all sensor firmwares for all build targets. build-kiss-radio-firmwares: Build all KISS radio firmwares for all build targets. - get-companion-firmwares-to-build: List canonical attached companion targets for release automation; dual-CDC boards use Full Companion instead of separate transport artifacts. + get-companion-firmwares-to-build: List canonical attached companion targets for release automation; Full Companion replaces separate transport artifacts where qualified. get-repeater-firmwares-to-build: List standard and specialized external-storage repeater targets for release automation. get-room-server-firmwares-to-build: List standard room-server targets for release automation. @@ -430,11 +430,15 @@ for section, options in data: M5Stack_Unit_C6L_companion_radio_ble Heltec_Wireless_Tracker_companion_radio_ble LilyGo_T3S3_sx1276_companion_radio_ble + LilyGo_Tlora_C6_companion_radio_ble_ Heltec_ct62_companion_radio_ble Meshadventurer_sx1262_companion_radio_ble Meshadventurer_sx1268_companion_radio_ble Heltec_Wireless_Paper_companion_radio_ble Heltec_E213_companion_radio_ble + Meshimi_companion_radio_ble_ + WHY2025_badge_companion_radio_ble_ + Xiao_C6_companion_radio_ble_ Xiao_S3_companion_radio_ble LilyGo_TETH_Elite_sx1262_companion_radio_ble LilyGo_T3S3_sx1262_companion_radio_ble @@ -443,6 +447,19 @@ for section, options in data: Tbeam_SX1262_companion_radio_ble Tbeam_SX1276_companion_radio_ble T_Beam_S3_Supreme_SX1262_companion_radio_ble + heltec_v4_expansionkit_tft_companion_radio_ble_femon + ) + # A few older targets predate the companion_radio_ naming + # convention. Keep their established PlatformIO recipe as the build base, + # but publish a consistently named Full target. SenseCAP Indicator keeps + # separate ESP-NOW and LoRa images because those are different physical + # radio layouts, not transport-only variants of one image. + local -a qualified_esp32_named_full_companion_specs=( + 'Generic_ESPNOW_comp_radio_usb|Generic_ESPNOW_companion_radio_full' + 'Heltec_E290_companion_usb_ble|Heltec_E290_companion_radio_full' + 'Heltec_T190_companion_radio_usb_ble_|Heltec_T190_companion_radio_full_' + 'SenseCapIndicator-ESPNow_comp_radio_usb|SenseCapIndicator-ESPNow_companion_radio_full' + 'SenseCapIndicator-LoRa_comp_radio_usb_wifi|SenseCapIndicator-LoRa_companion_radio_full' ) local -a qualified_nrf52_full_companion_bases=( GAT562_Mesh_Watch13_companion_radio_ble @@ -472,6 +489,28 @@ for section, options in data: PIO_ENV_BUILD_BASE_BY_NAME["$full_env"]="$env_name" done + local full_spec + for full_spec in "${qualified_esp32_named_full_companion_specs[@]}"; do + IFS='|' read -r env_name full_env <<<"$full_spec" + if [ -n "${PIO_ENV_PLATFORM_BY_NAME[$full_env]+x}" ]; then + continue + fi + if [ "${PIO_ENV_PLATFORM_BY_NAME[$env_name]:-}" != "ESP32_PLATFORM" ]; then + echo "Qualified Full Companion base is missing or not ESP32: ${env_name}" >&2 + return 1 + fi + SUPPORTED_PIO_ENVS+=("$full_env") + PIO_ENV_PLATFORM_BY_NAME["$full_env"]="ESP32_PLATFORM" + PIO_ENV_BOARD_BY_NAME["$full_env"]="${PIO_ENV_BOARD_BY_NAME[$env_name]}" + PIO_ENV_MQTT_BY_NAME["$full_env"]=0 + PIO_ENV_OTA_BY_NAME["$full_env"]=1 + PIO_ENV_SD_OTA_BY_NAME["$full_env"]=0 + PIO_ENV_QSPI_OTA_BY_NAME["$full_env"]=0 + PIO_ENV_FULL_BUILD_BY_NAME["$full_env"]=0 + PIO_ENV_FULL_WIFI_OTA_BY_NAME["$full_env"]="${PIO_ENV_FULL_WIFI_OTA_BY_NAME[$env_name]:-0}" + PIO_ENV_BUILD_BASE_BY_NAME["$full_env"]="$env_name" + done + for env_name in "${qualified_nrf52_full_companion_bases[@]}"; do full_env=${env_name/companion_radio_ble/companion_radio_full} if [ -n "${PIO_ENV_PLATFORM_BY_NAME[$full_env]+x}" ]; then @@ -492,6 +531,17 @@ for section, options in data: PIO_ENV_FULL_WIFI_OTA_BY_NAME["$full_env"]=0 PIO_ENV_BUILD_BASE_BY_NAME["$full_env"]="$env_name" done + + # Full Companion may compile the direct WiFi MQTT bridge as a saved, + # runtime-optional capability. It is still the canonical all-transport + # image, not an MQTT-only profile which the global MQTT build selector may + # replace or discard. + for env_name in "${SUPPORTED_PIO_ENVS[@]}"; do + if [ "${PIO_ENV_PLATFORM_BY_NAME[$env_name]:-}" = "ESP32_PLATFORM" ] \ + && is_companion_radio_full_target "$env_name"; then + PIO_ENV_MQTT_BY_NAME["$env_name"]=0 + fi + done fi } @@ -610,7 +660,7 @@ prompt_for_build_mode() { "Build all canonical firmwares (legacy setting aliases remain direct-build only)" "Build the canonical release matrix with unified FULL ESP32 USB + WiFi output (plus logging fallbacks where WiFi is unavailable)" "Build all repeater firmwares" - "Build canonical companion firmwares (dual-CDC Full replaces separate transports; power saving and FEM/RX gain are runtime configurable)" + "Build canonical companion firmwares (Full replaces separate transports; power saving and FEM/RX gain are runtime configurable)" "Build all chat room server firmwares" "Build all sensor firmwares" "Build FULL ESP32 firmwares (all features, USB logging, WiFi MQTT where available, and LoRa OTA)" @@ -2195,7 +2245,17 @@ normalize_resolved_targets_for_mqtt() { for target in "${candidates[@]}"; do candidate="" - if [ "${MQTT_BRIDGE_OVERRIDE,,}" == "on" ]; then + if is_companion_radio_full_target "$target"; then + # Full is already the board's one runtime-configurable transport image, + # so never swap it for a second artifact or compile a different feature + # set under the same name. Keep it for MQTT=off; for MQTT=on, keep only + # recipes which were explicitly qualified with direct MQTT support. + if [ "${MQTT_BRIDGE_OVERRIDE,,}" != "on" ] \ + || pio_env_option_contains "$(get_pio_build_env "$target")" \ + build_flags "WITH_MQTT_BRIDGE"; then + candidate=$target + fi + elif [ "${MQTT_BRIDGE_OVERRIDE,,}" == "on" ]; then candidate=$(get_mqtt_enabled_target "$target") || candidate="" else candidate=$(get_mqtt_disabled_target "$target") || candidate="" @@ -2242,6 +2302,16 @@ disable_debug_flags() { } apply_mqtt_bridge_override() { + local env_name=${1:-} + + # Full Companion is one immutable release artifact whose compiled + # capabilities are controlled by its qualified board recipe and then + # enabled or disabled at runtime. A matrix-wide MQTT choice must not mutate + # that artifact into another binary under the same filename. + if [ -n "$env_name" ] && is_companion_radio_full_target "$env_name"; then + return 0 + fi + case "${MQTT_BRIDGE_OVERRIDE,,}" in on) export PLATFORMIO_BUILD_FLAGS="${PLATFORMIO_BUILD_FLAGS} -DWITH_MQTT_BRIDGE=1" @@ -2338,8 +2408,8 @@ disable_usb_logging_for_mqtt() { local env_name=$1 # Full Companion always compiles diagnostics behind a saved runtime gate. - # Dual-CDC boards write them to CDC 1. Single-TTY boards first switch CDC 0 - # into an input-capable terminal so plaintext cannot mix with framed traffic. + # ESP32 uses one TTY and switches it into an input-capable logging terminal, + # so plaintext cannot mix with framed traffic. nRF52 retains dedicated CDC 1. if is_companion_radio_full_target "$env_name"; then return 0 fi @@ -2393,36 +2463,13 @@ is_nrf52_companion_radio_full_target() { && is_companion_radio_full_target "$1" } -is_esp32_dual_cdc_companion_radio_full_target() { - is_esp32_companion_radio_full_target "$1" || return 1 - case "${1,,}" in - lilygo_tbeam_1w_companion_radio_full|\ - station_g2_companion_radio_full|\ - station_g3_esp32_companion_radio_full|\ - xiao_s3_wio_companion_radio_full|\ - heltec_tracker_v2_companion_radio_full_femon|\ - meshnology_w12_companion_radio_full|\ - nibble_screen_connect_companion_radio_full_|\ - nibble_zero_connect_companion_radio_full_|\ - heltec_v4_2_v4_3_companion_radio_full_femon|\ - heltec_v4_3_companion_radio_full_femoff|\ - heltec_v4_tft_companion_radio_full_femon|\ - heltec_v4_3_tft_companion_radio_full_femoff|\ - heltec_v4_r8_companion_radio_full|\ - heltec_v4_r8_tft_companion_radio_full) - return 0 - ;; - *) return 1 ;; - esac -} - requires_esp32_arduino3_framework() { local env_name=$1 local pio_env_name=$2 - if is_esp32_dual_cdc_companion_radio_full_target "$env_name"; then - return 0 - fi + # RC32 still uses its board-qualified Arduino 3 package independently of + # the removed dual-CDC Full profile. ESP32-C6 also inherently uses A3; + # ordinary ESP32/S3 Full images return to the shared A2 platform. case "${env_name,,}:${pio_env_name,,}" in heltec_rc32_*:*|*:heltec_rc32_*) return 0 ;; *) return 1 ;; @@ -2694,8 +2741,7 @@ declare_build_capability_contract() { record_build_expectation "companion.bluetooth" \ "Companion: starting Bluetooth" record_build_expectation "companion.usb_logging" "get usb.logging" - if is_nrf52_companion_radio_full_target "$env_name" \ - || is_esp32_dual_cdc_companion_radio_full_target "$env_name"; then + if is_nrf52_companion_radio_full_target "$env_name"; then record_build_expectation "companion.dedicated_usb_logging" \ "get usb.logging" fi @@ -2705,6 +2751,11 @@ declare_build_capability_contract() { record_build_expectation "companion.wifi_ota_seeder" \ "OTA seeder listening on :" record_build_expectation "web.webconfig" "start webconfig" + pio_env_name=$(get_pio_build_env "$env_name") + if pio_env_option_contains "$pio_env_name" build_flags \ + "WITH_MQTT_BRIDGE"; then + record_build_expectation "companion.direct_mqtt" "mqtt.status" + fi else record_build_expectation "companion.usb_mota_source" "ota folder on" record_build_expectation "companion.ble_mota_source" \ @@ -3137,7 +3188,8 @@ apply_companion_radio_full_profile() { # command to Binary Companion. `motatool serve --serial` switches it into # 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. + # an independent Companion link. ESP32 intentionally does not use this + # dual-CDC capability. append_platformio_build_unflags "-UOTA_FOLDER_SERIAL" export PLATFORMIO_BUILD_FLAGS="${PLATFORMIO_BUILD_FLAGS} -DOTA_FOLDER_SERIAL=1 -DCOMPANION_FEATURE_USB_MOTA_SOURCE=1 -DCOMPANION_FEATURE_BLE_MOTA_SOURCE=1 -DCOMPANION_FEATURE_DEDICATED_USB_LOGGING=1 -DCFG_TUD_CDC=2 -DMESH_DUAL_CDC_LOGGING=1 -DMESH_DEBUG=1 -DMESH_PACKET_LOGGING=1" @@ -3151,16 +3203,6 @@ apply_companion_radio_full_profile() { return 0 fi - if is_esp32_dual_cdc_companion_radio_full_target "$env_name"; then - # Qualified ESP32-S3 boards route their data connector to the native USB - # peripheral. Arduino-ESP32 3.x supplies two real TinyUSB CDC ACM - # instances: CDC 0 carries framed Companion traffic and accepts the - # flashing reboot gesture; CDC 1 is a write-only plaintext diagnostics - # stream and cannot reboot the board. - append_platformio_build_unflags "-DARDUINO_USB_MODE=1 -DARDUINO_USB_CDC_ON_BOOT=0" - export PLATFORMIO_BUILD_FLAGS="${PLATFORMIO_BUILD_FLAGS} -DARDUINO_USB_MODE=0 -DARDUINO_USB_CDC_ON_BOOT=1 -DCOMPANION_FEATURE_DEDICATED_USB_LOGGING=1 -DMESH_DUAL_CDC_LOGGING=1 -DMESH_DEBUG=1 -DMESH_PACKET_LOGGING=1" - fi - # ESP32 keeps both source transports. TCP remains the normal unattended # path; a host may explicitly hold the native USB port in Companion terminal # mode when WiFi is unavailable and serve the same folder protocol there. @@ -3636,7 +3678,7 @@ build_firmware() { export PLATFORMIO_BUILD_FLAGS="${original_platformio_build_flags} -DFIRMWARE_BUILD_DATE='\"${firmware_build_date}\"' -DFIRMWARE_BUILD_EPOCH=${firmware_build_epoch} -DFIRMWARE_VERSION='\"${embedded_version_string}\"' -DOTA_VARIANT='\"${env_name}\"'${mota_target_flag}" disable_debug_flags "$env_name" apply_debug_overrides "$env_name" - apply_mqtt_bridge_override + apply_mqtt_bridge_override "$env_name" disable_usb_logging_for_mqtt "$env_name" apply_merged_standard_usb_logging_profile "$env_name" apply_lora_ota_override "$env_name" @@ -3838,17 +3880,45 @@ get_esp32_full_companion_replacement() { [ "${PIO_ENV_PLATFORM_BY_NAME[$1]:-}" = "ESP32_PLATFORM" ] || return 1 case "$env_name" in + heltec_v3_companion_radio_wifi_mqtt|\ + heltec_v4_companion_radio_wifi_mqtt_femon|\ + heltec_v4_3_companion_radio_wifi_mqtt_femoff) ;; *companion_radio_wifi_mqtt*) return 1 ;; *companion_radio_usb*|*companion_radio_ble*|*companion_radio_wifi*|\ - *companion_radio_serial*|*companion_radio_ethernet*) ;; + *companion_radio_serial*|*companion_radio_ethernet*|*comp_radio_usb*|\ + heltec_e290_companion_ble|heltec_e290_companion_usb|\ + heltec_e290_companion_usb_ble) ;; *) return 1 ;; esac # Full Companion includes USB, BLE, and ordinary WiFi Companion. Map legacy # FEM-default aliases to the one runtime-configurable image for each physical - # V4 display/radio layout. Expansion-kit targets intentionally do not match: - # they have distinct sensor wiring and no corresponding Full recipe. + # V4 display/radio layout. The expansion-kit TFT remains a distinct Full + # recipe because it has different sensor wiring from both base display + # layouts. case "$env_name" in + heltec_v3_companion_radio_wifi_mqtt) + full_env=Heltec_v3_companion_radio_full + ;; + generic_espnow_comp_radio_*) + full_env=Generic_ESPNOW_companion_radio_full + ;; + heltec_e290_companion_*) + full_env=Heltec_E290_companion_radio_full + ;; + heltec_t190_companion_radio_*) + full_env=Heltec_T190_companion_radio_full_ + ;; + sensecapindicator-espnow_comp_radio_*) + full_env=SenseCapIndicator-ESPNow_companion_radio_full + ;; + sensecapindicator-lora_comp_radio_*) + full_env=SenseCapIndicator-LoRa_companion_radio_full + ;; + heltec_v4_3_expansionkit_tft_companion_radio_*|\ + heltec_v4_expansionkit_tft_companion_radio_*) + full_env=heltec_v4_expansionkit_tft_companion_radio_full_femon + ;; heltec_v4_r8_tft_companion_radio_*) full_env=heltec_v4_r8_tft_companion_radio_full ;; diff --git a/docs/_javascript/firmware_picker.js b/docs/_javascript/firmware_picker.js index 3f77a540..1ae0791a 100644 --- a/docs/_javascript/firmware_picker.js +++ b/docs/_javascript/firmware_picker.js @@ -388,22 +388,9 @@ if (!profile || profile.role !== "companion" || profile.mode !== "full") { return false; } - if (profile.installKinds.includes("zip")) return true; - const target = String(profile.target || "").toLowerCase(); - if (/^heltec_v4(?:_2_v4_3|_3)?(?:_r8)?(?:_tft)?_companion_radio_full(?:_|$)/ - .test(target)) { - return true; - } - return [ - "lilygo_tbeam_1w_companion_radio_full", - "station_g2_companion_radio_full", - "station_g3_esp32_companion_radio_full", - "xiao_s3_wio_companion_radio_full", - "heltec_tracker_v2_companion_radio_full_femon", - "meshnology_w12_companion_radio_full", - "nibble_screen_connect_companion_radio_full_", - "nibble_zero_connect_companion_radio_full_", - ].includes(target); + // Native nRF52 Serial DFU packages use .zip. ESP32 Full Companion always + // has one USB TTY and never receives the dedicated-port capability. + return profile.installKinds.includes("zip"); } function isFullCompanion(profile) { @@ -487,11 +474,12 @@ return (profiles || []).map(function (profile) { if (!isFullCompanion(profile)) return profile; - // Every Full Companion has a runtime USB logging mode. Dual-CDC targets - // add an independent plaintext interface after reboot; single-TTY - // targets switch their existing port between framed and plaintext modes. + // Every Full Companion has a runtime USB logging mode. nRF52 adds an + // independent plaintext interface after reboot. Every ESP32 target + // switches its one TTY between framed and plaintext modes. profile.logging = "usb-runtime"; profile.loggingModes = ["none", "usb"]; + profile.dedicatedUsbLogging = false; if (isDualCdcFullCompanion(profile)) { profile.dedicatedUsbLogging = true; } @@ -830,7 +818,7 @@ ); } else { extra.push( - "Full Companion starts with USB logging off and Binary Companion on its single TTY. Enter the text terminal and use set usb.logging on for plaintext logs; that TTY still accepts set usb.logging off and automatically returns to Binary Companion after the reply." + "Full Companion starts in its ASCII terminal with USB logging off. Use set usb.logging on for plaintext logs; set usb.logging off stops those logs but stays in normal ASCII mode. Then send +++MESHCORE-TERM-STOP or let a Companion app send a valid framed probe to switch the TTY to Binary Companion." ); } } diff --git a/docs/cli_build_matrix.md b/docs/cli_build_matrix.md index ec850f5a..fa40eefe 100644 --- a/docs/cli_build_matrix.md +++ b/docs/cli_build_matrix.md @@ -72,8 +72,8 @@ retain 50 because their MQTT discovery tables are constrained by internal DRAM. | ESP32 MQTT observer or ESP-NOW bridge | Always uses the expanded FULL partition profile. The build never substitutes a reduced CLI to fit the legacy application slot. | | FULL ESP32 USB + WiFi | Uses the matching MQTT target with packet logging on, verbose debug off, and the complete command surface supported by that role and hardware. `get/set logging.output off\|usb\|wifi\|both` selects and persists the active output paths. | | FULL ESP32 logging fallback | Uses the matching non-MQTT target only when no WiFi MQTT sibling exists, with debug and packet logging enabled and the complete command surface supported by that role and hardware. Its persistent USB gate also covers output-off operation, avoiding a second FULL ESP-NOW image. | -| Dual-CDC Full Companion | nRF52 and qualified native-USB ESP32-S3 Full images use one physical USB connection. Fresh installs expose only interface `00`; it starts as an ASCII terminal and automatically hands a complete `<` frame to framed Companion. The same interface also carries exclusive serial mOTA traffic. Enabling logging and rebooting adds interface `02` for plaintext logs. They also provide BLE and source-only LoRa OTA; ESP32 additionally provides WiFi. `get/set usb.logging` persistently controls whether the logging interface is present. | -| Single-TTY Full Companion | ESP32 Full images without dual CDC start with the ASCII terminal on their one TTY and automatically hand a complete `<` frame to framed Companion. `set usb.logging on` switches it to an input-capable plaintext logging terminal; `set usb.logging off` replies and then restores framed Companion automatically. A saved logging-on setting starts directly in that logging terminal and disables automatic frame detection. BLE, WiFi, and source-only LoRa OTA remain available. | +| nRF52 dual-CDC Full Companion | Fresh installs expose only interface `00`; it starts as an ASCII terminal and automatically hands a complete `<` frame to framed Companion. The same interface also carries exclusive serial mOTA traffic. Enabling logging and rebooting adds interface `02` for plaintext logs. BLE and source-only LoRa OTA remain available. `get/set usb.logging` persistently controls whether the logging interface is present. | +| ESP32 single-TTY Full Companion | Every ESP32 Full image starts with the ASCII terminal on its one USB TTY and automatically hands a complete `<` frame to framed Companion. `set usb.logging on` switches that TTY to an input-capable plaintext logging terminal and makes framed Companion unavailable on USB; `set usb.logging off` stops logging but leaves the TTY in normal ASCII mode. The terminal stop token or a valid incoming framed probe then performs the ordinary switch to Binary Companion. A saved logging-on setting starts directly in that logging terminal and disables automatic frame detection. BLE, WiFi, and source-only LoRa OTA remain available. ESP32 Full uses Arduino-ESP32 2.x where supported; RC32 and ESP32-C6 keep their board-required Arduino 3.x platform but still expose only one TTY. | | `no_external_sensors` | Removes optional external-sensor drivers and their settings; it does not remove core repeater discovery, routing, or runtime RS-232 commands. RAK3401 and RAK4631 profiles retain the four common INA I2C voltage/current monitors. GPS-preserving RAK nRF52 OTA profiles retain their GPS commands and provider; RAK4631 defaults the bridge to UART 2 because GPS uses UART 1. Legacy target suffixes remain stable for OTA identity compatibility. | `logging`, `OTA`, and `FULL` describe independent build features in historical @@ -98,10 +98,11 @@ available from a canonical image: separate USB, BLE, ordinary WiFi, hardware-serial, Ethernet Companion, and USB packet-logging Companion artifacts. It provides BLE and source-only LoRa OTA; ESP32 also provides ordinary WiFi. - Dual-CDC builds keep framed traffic on interface `00` and add logging on - interface `02` after a reboot. Single-TTY builds switch interface `00` into - an input-capable logging terminal and restore Binary Companion when that - terminal session ends. + nRF52 keeps framed traffic on interface `00` and can add logging on interface + `02` after a reboot. Every ESP32 Full build instead switches its one USB TTY + into an input-capable logging terminal. Turning logging off restores the + normal ASCII terminal; the usual stop token or a valid framed probe then + switches it to Binary Companion. Its LoRa OTA support is source-only: it can serve a host file to another node but has no staging store and cannot update itself over LoRa. Installing an ESP32 Full Companion may require one merged-image erase/flash @@ -127,8 +128,8 @@ The old aliases still work with `build-firmware` and collapsed; they retain their exact storage, bootloader, role, and target identity contracts. Companion boards keep transport-specific canonical images only when no exact Full recipe has passed the combined flash/RAM qualification. -Dual CDC is not required: a qualified single-TTY Full image safely makes Binary -Companion and plaintext USB logging mutually exclusive. +ESP32 deliberately uses one TTY: Binary Companion and plaintext USB logging +are mutually exclusive there. nRF52 retains its optional second CDC port. ## Complete CLI policy diff --git a/docs/cli_command_availability.md b/docs/cli_command_availability.md index b2a10341..88cd2046 100644 --- a/docs/cli_command_availability.md +++ b/docs/cli_command_availability.md @@ -72,7 +72,7 @@ fix, no WiFi connection, an inactive bridge, or an nRF52 bootloader without | Statistics | [`set telemetry.gps`; `get/set/send telemetry.tx`](cli_commands.md#read-repeater-telemetry-history) | Non-STM32 repeater; GPS setting requires a provider; remote access requires administrator | Yes | Yes | Yes | | Logging | [`log start`; `log stop`; `log erase`](cli_commands.md#logging) | Storage-backed roles retain data; other roles can return empty data | Yes | Yes | Yes | | Logging | [`log`](cli_commands.md#print-the-captured-log-to-the-serial-terminal) | Local serial | Serial | Serial | Serial | -| Logging | [`get/set usb.logging`; unified FULL `get/set logging.output`](cli_commands.md#control-live-usb-logging) | Ordinary safe-USB artifacts; CommonCLI USB gate is persistent; unified ESP32 FULL selects off/USB/WiFi/both; Full Companion uses either a reboot-controlled second CDC or an input-capable single-TTY logging terminal | Yes | Yes | No | +| Logging | [`get/set usb.logging`; unified FULL `get/set logging.output`](cli_commands.md#control-live-usb-logging) | Ordinary safe-USB artifacts; CommonCLI USB gate is persistent; unified ESP32 FULL selects off/USB/WiFi/both; nRF52 Full Companion can add a reboot-controlled second CDC | Yes | Yes | No | | Radio | [`get radio`; `set radio ...`](cli_commands.md#view-or-change-this-nodes-radio-parameters) | All text CLI roles | Yes | Yes | Yes | | Radio | [`get tx`; `set tx `](cli_commands.md#view-or-change-this-nodes-transmit-power) | Board TX-power limits apply | Yes | Yes | Yes | | Radio | [`tempradio ...`; `normalradio`](cli_commands.md#change-the-radio-parameters-for-a-set-duration) | Full parser | Yes | Yes | Yes | @@ -236,7 +236,7 @@ fix, no WiFi connection, an inactive bridge, or an nRF52 bootloader without | Statistics | [`set telemetry.gps`; `get/set/send telemetry.tx`](cli_commands.md#read-repeater-telemetry-history) | Non-STM32 repeater; GPS setting requires a provider; remote access requires administrator | Yes | Yes | Yes | Yes | Yes | | Logging | [`log start`; `log stop`; `log erase`](cli_commands.md#logging) | Storage-backed roles retain data | Yes | Yes | Yes | Yes | Yes | | Logging | [`log`](cli_commands.md#print-the-captured-log-to-the-serial-terminal) | Local serial | Serial | Serial | Serial | Serial | Serial | -| Logging | [`get/set usb.logging`; unified FULL `get/set logging.output`](cli_commands.md#control-live-usb-logging) | Ordinary safe-USB artifacts; CommonCLI USB gate is persistent; unified ESP32 FULL selects off/USB/WiFi/both; Full Companion uses either a reboot-controlled second CDC or an input-capable single-TTY logging terminal | Yes | Yes | No | No | Yes | +| Logging | [`get/set usb.logging`; unified FULL `get/set logging.output`](cli_commands.md#control-live-usb-logging) | Ordinary safe-USB artifacts; CommonCLI USB gate is persistent; unified ESP32 FULL selects off/USB/WiFi/both; every ESP32 Full Companion uses an input-capable single-TTY logging terminal with framed USB Companion disabled while logging | Yes | Yes | No | No | Yes | | Radio | [`get radio`; `set radio ...`](cli_commands.md#view-or-change-this-nodes-radio-parameters) | All text CLI roles | Yes | Yes | Yes | Yes | Yes | | Radio | [`get tx`; `set tx `](cli_commands.md#view-or-change-this-nodes-transmit-power) | Board TX-power limits apply | Yes | Yes | Yes | Yes | Yes | | Radio | [`tempradio ...`; `normalradio`](cli_commands.md#change-the-radio-parameters-for-a-set-duration) | Full parser | Yes | Yes | Yes | Yes | Yes | diff --git a/docs/cli_commands.md b/docs/cli_commands.md index 2a60a831..4134a94e 100644 --- a/docs/cli_commands.md +++ b/docs/cli_commands.md @@ -624,26 +624,29 @@ Binary `get/set` parameter namespace. Open interface `00`, send `meshcli ... get usb.logging` directly can instead return `Unknown var usb.logging` because that is a different protocol operation. -Dual-CDC Full Companion instead defaults logging to off and enumerates only -USB interface `00`, which carries Companion, terminal, and serial mOTA traffic. -Enabling logging adds interface `02`, its dedicated plaintext logging port, on -the next boot. Disabling it removes interface `02` on the next boot. A command -without the optional `reboot` argument saves the choice and reports that a -reboot is required when the USB interface count must change. The exact +nRF52 Full Companion defaults logging to off and enumerates only USB interface +`00`, which carries Companion, terminal, and serial mOTA traffic. Enabling +logging adds interface `02`, its dedicated plaintext logging port, on the next +boot. Disabling it removes interface `02` on the next boot. A command without +the optional `reboot` argument saves the choice and reports that a reboot is +required when the USB interface count must change. The exact `set usb.logging on reboot` and `set usb.logging off reboot` forms save the -choice, send their reply, and reboot one second later only when needed. This -behavior includes nRF52 and dual-CDC native-USB ESP32-S3 Full images. +choice, send their reply, and reboot one second later only when needed. -On a single-TTY ESP32 Full Companion, enter the USB text terminal and use +On every ESP32 Full Companion, enter the USB text terminal and use `set usb.logging on` to turn that TTY into a logging-repeater-style plaintext -stream. It remains an input-capable CLI, so `set usb.logging off` works on the -same TTY and automatically restores Binary Companion after its reply. No -reboot is needed because the USB interface count does not change. +stream. Framed Binary Companion is unavailable on USB while logging is on. The +TTY remains an input-capable CLI, so `set usb.logging off` works on the same +TTY. After its reply, logging stops and the TTY remains in the normal ASCII +terminal, just as it does after a fresh Full installation. Send +`+++MESHCORE-TERM-STOP`, or let a Companion app send a valid framed probe, to +switch it to Binary Companion. No reboot is needed because the USB interface +count does not change. -Turning USB logging off does not disable CLI replies. Dual-CDC builds keep -Companion frames active on interface `00`; single-TTY builds resume frames when -their terminal session ends. This setting does not change the node-storage -capture controlled by `log start` and `log stop`. +Turning USB logging off does not disable CLI replies. nRF52 keeps Companion +frames active on interface `00`; ESP32 resumes the ordinary ASCII/Binary +switcher after the logging terminal turns logging off. This setting does not +change the node-storage capture controlled by `log start` and `log stop`. Unified ESP32 FULL builds add one saved selector for both output paths: diff --git a/docs/companion_radio_full.md b/docs/companion_radio_full.md index c23bfb94..1fad6112 100644 --- a/docs/companion_radio_full.md +++ b/docs/companion_radio_full.md @@ -11,7 +11,7 @@ firmware as an mOTA image. | USB Binary Companion | Yes | Yes | | BLE Binary Companion | Yes | Yes | | USB ASCII terminal | Yes | Yes | -| Dedicated USB plaintext logging | Qualified native-USB ESP32-S3 profiles | Yes | +| Dedicated USB plaintext logging | No - shares the one USB TTY | Yes | | Host-backed LoRa mOTA source | WiFi TCP 5001 | Exclusive USB mode or encrypted BLE | | WiFi Companion/WebConfig | Yes | No - nRF52840 has no WiFi | | Hardware serial Companion | On targets with assigned serial pins | On targets with assigned serial pins | @@ -29,13 +29,15 @@ passed the combined-transport size check: same board can safely add its platform's remaining transports. It never substitutes the pin map or peripherals from another board. -The measured ESP32 additions are M5Stack Unit C6L, Heltec Wireless Tracker, -Wireless Paper, E213, and CT62; LilyGo T3S3 SX1262/SX1276, T-Deck, TETH Elite, -classic T-Beam SX1262/SX1276, and T-Beam S3 Supreme; Ebyte EoRa-S3; -Meshadventurer SX1262/SX1268; and XIAO S3. The measured nRF52 additions are -GAT562 Mesh Watch13, LilyGo T-Echo Lite, LilyGo T-Impulse Plus, and Wio Tracker -L1 E-Ink. Their old transport-specific names remain available for explicit -compatibility builds, but the Full image is the canonical release artifact. +The measured ESP32 additions are M5Stack Unit C6L; XIAO C6/S3, Meshimi, +WHY2025 Badge, LilyGo T-LoRa C6, T3S3 SX1262/SX1276, T-Deck, TETH Elite, +classic T-Beam SX1262/SX1276, and T-Beam S3 Supreme; Heltec Wireless Tracker, +Wireless Paper, E213, E290, T190, CT62, and V4 expansion-kit TFT; Generic +ESP-NOW; SenseCAP Indicator ESP-NOW/LoRa; Ebyte EoRa-S3; and Meshadventurer +SX1262/SX1268. The measured nRF52 additions are GAT562 Mesh Watch13, LilyGo +T-Echo Lite, LilyGo T-Impulse Plus, and Wio Tracker L1 E-Ink. Their old +transport-specific names remain available for explicit compatibility builds, +but the Full image is the canonical release artifact. List the available targets: @@ -69,8 +71,8 @@ hardware-serial, Ethernet Companion, Terminal Chat, and USB-only packet-logging release artifacts whenever the exact board supports those combined transports. Direct builds of the legacy targets remain available. RAK4631 repeater and room server Ethernet builds remain separate because they are different standalone -roles, not Companion transports. Dual-CDC builds separate framed traffic and logs; -single-TTY builds make those modes mutually exclusive. In WebConfig, use the +roles, not Companion transports. nRF52 separates framed traffic and logs; +ESP32 makes those modes mutually exclusive on its one USB TTY. In WebConfig, use the **FEM RX boost** switch. From the text terminal (USB, or TCP 5002 on ESP32), use: ```text @@ -103,8 +105,15 @@ set display.rotation 0 `0` resets the screen to that board's compiled default orientation. -Heltec E290 and T190 use one `usb_ble` Companion artifact for simultaneous USB -and BLE rather than publishing separate USB-only and BLE-only images. +Heltec E290 and T190 now use their Full Companion artifacts for simultaneous +USB, BLE, and WiFi. Their older `usb_ble`, USB-only, and BLE-only names remain +available only as explicit compatibility builds. + +Heltec V3 and base OLED V4 Full Companion also include the former direct WiFi +MQTT Companion capability. Configure and enable MQTT at runtime through +WebConfig; the canonical release therefore publishes the Full image instead of +a second `companion_radio_wifi_mqtt` image. V4 TFT and expansion-kit layouts +remain separate hardware images because their display and I2C wiring differs. Device power saving is separate from LoRa RXPS. It can be changed in WebConfig with the **Device power saving** switch or from the text terminal: @@ -196,8 +205,8 @@ started before WiFi on any combined ESP32 WiFi+BLE Companion to avoid heap fragmentation. Compile-time prerequisite checks reject inconsistent feature flags. After linking, the capability sidecar verifies USB, BLE, the OTA CLI, TempRadio, and each platform's host-folder transport; it also verifies the TCP -terminal, WebConfig, and WiFi seeder on ESP32, plus dedicated logging whenever -the board uses dual CDC. +terminal, WebConfig, and WiFi seeder on ESP32, plus dedicated logging on +nRF52. On 4 MB ESP32 boards, the full target uses a single 3 MB application partition so WiFi, BLE, WebConfig, and source-only mOTA fit together. The T-Beam 1W Full @@ -318,23 +327,31 @@ WiFi credential, status, WebConfig, CLI-tab, and power-save controls. Every Full Companion provides persistent `get/set usb.logging` and starts with logging off on a fresh installation. -### Single USB serial port +### ESP32 single USB serial port -On an ESP32 Full Companion without dual CDC, interface `00` has two exclusive -modes. It starts as the ASCII terminal unless a saved logging-on preference -boots directly into the logging terminal. If it is already binary, enter its -text terminal with `+++MESHCORE-TERM-START`, then run `set usb.logging on`; the same TTY emits -plaintext packet/debug logs and continues accepting CLI commands, including -`set usb.logging off`. Turning it off sends the command reply and then returns -that TTY to Binary Companion automatically, including on USB-UART bridges that -cannot detect a cable disconnect. A saved logging-on preference boots directly -into this input-capable logging terminal. BLE and Wi-Fi Companion remain -available while USB is logging. +Every ESP32 Full Companion exposes one USB TTY with two exclusive modes. It +starts as the ASCII terminal unless a saved logging-on preference boots +directly into the logging terminal. If it is already binary, enter its text +terminal with `+++MESHCORE-TERM-START`, then run `set usb.logging on`; the same +TTY emits plaintext packet/debug logs and continues accepting CLI commands, +including `set usb.logging off`. Framed Binary Companion is unavailable on USB +while logging owns the TTY. Turning logging off sends the command reply, stops +the logs, and leaves that TTY in the normal ASCII terminal, matching a fresh +Full installation. Send `+++MESHCORE-TERM-STOP`, or let a Companion app send a +valid framed probe, to switch it to Binary Companion afterward. A saved +logging-on preference boots directly into this input-capable logging terminal. +BLE and Wi-Fi Companion remain available while USB is logging. -### Dual USB serial ports +ESP32 Full Companion uses the repository's Arduino-ESP32 2.x platform base +where the board supports it. RC32 and ESP32-C6 retain their board-required +Arduino 3.x platform, but follow the same one-TTY policy. No ESP32 Full image +creates an optional second CDC interface. The single-TTY behavior applies to +native-USB ESP32-S3 boards and boards using a USB-UART bridge. -Current nRF52 Full Companion and qualified native-USB ESP32-S3 Full Companion -firmware can expose two CDC ACM serial interfaces on one physical USB cable: +### nRF52 dual USB serial ports + +Current nRF52 Full Companion firmware can expose two CDC ACM serial interfaces +on one physical USB cable: - USB interface `00` is the primary Companion, text-terminal, and serial-mOTA source port. It is always present, starts in ASCII after boot, and @@ -362,35 +379,30 @@ When logging is on, Linux normally shows two `/dev/ttyACM*` devices. Match the s `ID_USB_INTERFACE_NUM`, rather than assuming which tty number is assigned. On Windows they appear as two COM ports; identify them by USB interface instead of depending on a particular COM number. The nRF52 bootloader temporarily exposes -its normal DFU serial interface during an update. Qualified S3 boards -temporarily expose the ESP32-S3 ROM USB-JTAG serial port during a wired flash. +its normal DFU serial interface during an update. -Opening the logging port prints `MeshCore USB logging port` followed by its -portable identity, `USB CDC 1; interface 02; Linux stable suffix: -if02`. +Opening the nRF52 logging port prints `MeshCore USB logging port` followed by +its portable identity, `USB CDC 1; interface 02; Linux stable suffix: -if02`. `get usb.logging` reports the same endpoint. Firmware cannot print the exact `/dev/ttyACM*` or `COM*` name because Linux, macOS, or Windows assigns that name after USB enumeration; use the `*-if02` link on Linux to obtain the exact path. For example, `readlink -f /dev/serial/by-id/*-if02` prints the host-assigned `/dev/ttyACM*` name. -Dual-CDC ESP32-S3 targets are Heltec V4, T-Beam 1W, Station G2/G3, XIAO S3 -WIO, Heltec Tracker V2, Meshnology W12, and Nibble Screen/Zero Connect. The -base Heltec V4 profile has completed live two-interface, ROM-flashing, and -logging-off one-interface validation. Full recipes with only one usable TTY -still replace separate transport images; they use the exclusive terminal/log -mode above because firmware cannot add a second interface to a USB-UART bridge -or a single-port USB peripheral. - Every ESP32-S3 Full Companion image uses DIO flash mode, including the RAK3112 -and RC32 profiles that do not expose dual CDC. The S3 ROM supports DIO while +and RC32 profiles. The S3 ROM supports DIO while loading the software bootloader, and some flash configurations fail before the application starts when a merged image inherits QIO. DIO trades some maximum flash-read throughput for compatibility; it does not change a board's PSRAM type or any ordinary non-Full firmware profile. -Point MeshCore Companion software, `meshcli`, and `motatool` at interface `00`. -When enabled and rebooted, point a plaintext reader or USB-connected MQTT -service at interface `02`. +On nRF52, point MeshCore Companion software, `meshcli`, and `motatool` at +interface `00`. When enabled and rebooted, point a plaintext reader or +USB-connected MQTT service at interface `02`. On ESP32, use its only USB TTY +for Binary Companion while logging is off, or for the plaintext CLI/logger +while logging is on; close one consumer before switching modes. Turning +logging off returns to ASCII, so the normal stop token or a valid framed probe +is still required before Binary Companion owns the port. ESP32 Full Companion exposes this same text terminal on TCP port 5002. Connect with `nc DEVICE_IP 5002`; no USB control token is needed. USB terminal mode and diff --git a/docs/firmware_picker.md b/docs/firmware_picker.md index eda1f455..d61d02c1 100644 --- a/docs/firmware_picker.md +++ b/docs/firmware_picker.md @@ -178,12 +178,18 @@ board-qualified serial or Ethernet Companion transport. Bulk builds therefore omit separate attached-transport, Terminal Chat, and USB-logging artifacts whenever the exact Full recipe exists. RAK4631 repeater and room-server Ethernet images remain separate roles. Fresh installs default to logging off. +Heltec V3 and base OLED V4 Full images also contain the former direct Wi-Fi +MQTT Companion capability, configured at runtime through WebConfig, so their +separate `companion_radio_wifi_mqtt` artifacts are omitted from canonical +builds as well. When Full Companion does not fit but a matching USB Companion does, that USB artifact also supplies Terminal Chat and replaces its standalone release image. -Heltec E290 and T190 publish a combined USB + BLE Companion. SSD1306 Full -Companion builds use `set display.rotation 90|180|270`; `0` restores the board -default, so a separate rotated release image is not recommended. +Heltec E290 and T190 now publish one Full USB + BLE + WiFi Companion; their old +combined and single-transport names are explicit-build compatibility aliases. +SSD1306 Full Companion builds use `set display.rotation 90|180|270`; `0` +restores the board default, so a separate rotated release image is not +recommended. Ordinary non-OTA roles also use one artifact for normal operation and USB logging. Select the saved mode with `set usb.logging off|on`; no `-logging-` @@ -191,23 +197,22 @@ artifact is emitted. KISS, BLE-only Companion, and constrained LoRa-OTA receiver images retain their protocol/partition contracts and do not inherit plaintext USB logging. -Dual-CDC nRF52 and qualified native-USB ESP32-S3 builds keep the multi-role -primary interface on `00`; it starts as an ASCII terminal and automatically -hands a complete `<` frame to Binary Companion. `set usb.logging on reboot` -adds plaintext interface `02`. -Single-TTY ESP32 builds instead use `set usb.logging on` to switch that TTY to -an input-capable plaintext logging terminal. `set usb.logging off` stops the -logs and returns the TTY to Binary Companion after its reply. BLE and Wi-Fi -remain usable while the USB TTY is logging. +nRF52 Full Companion keeps the multi-role primary interface on `00`; it starts +as an ASCII terminal and automatically hands a complete `<` frame to Binary +Companion. `set usb.logging on reboot` adds its plaintext interface `02`. -The dual-CDC ESP32-S3 subset includes Heltec V4, T-Beam 1W, Station G2/G3, -XIAO S3 WIO, Heltec Tracker V2, Meshnology W12, and Nibble Screen/Zero Connect -layouts. Existing single-TTY Full recipes include RAK3112, Heltec RC32, -Heltec V3/WSL3, ThinkNode M2/M5/M7/M9, Heltec V2, LilyGo T-LoRa V2.1.1.6, -and XIAO C3. Additional qualified single-TTY ESP32 profiles include M5Stack -Unit C6L, Heltec Wireless Tracker/Paper/E213/CT62, LilyGo T3S3 -SX1262/SX1276, T-Deck, TETH Elite, classic T-Beam SX1262/SX1276, T-Beam S3 -Supreme, Ebyte EoRa-S3, Meshadventurer SX1262/SX1268, and XIAO S3. +Every ESP32 Full Companion instead exposes one USB TTY. Logging is off by +default, so the TTY serves the ASCII/Binary Companion switcher. Use +`set usb.logging on` to turn that same TTY into an input-capable plaintext +CLI/logging stream; framed Binary Companion is unavailable on USB while +logging owns it. `set usb.logging off` stops the logs and leaves the TTY in +the normal ASCII terminal, matching a fresh Full installation. Send +`+++MESHCORE-TERM-STOP`, or let a Companion app send a valid framed probe, to +switch from there to Binary Companion. BLE and Wi-Fi Companion remain usable +while the USB TTY is logging. ESP32 Full builds use the repository's +Arduino-ESP32 2.x base where the board supports it; RC32 and ESP32-C6 retain +their board-required Arduino 3.x platform but still expose only one USB TTY. +A second ESP32 CDC interface is not part of the release profile. ## Installation methods @@ -244,11 +249,11 @@ saving, controllable FEM receive gain, and radio-chip receive gain are saved settings rather than separate recommended firmware files. Do not substitute a similarly named physical target. -For hardware with a dual-CDC Full Companion image, the picker recommends that -one normal image instead of separate USB, BLE, ordinary WiFi, and USB-logging -images. Full Companion provides the attached transports and a dedicated -plaintext logging port when enabled without mixing logs into framed Companion -traffic. Logging is off by default, so only primary interface `00` appears; it -starts in ASCII and automatically changes to framed Companion when a complete -`<` frame arrives. Enabling logging and rebooting adds interface `02`. +The picker recommends one Full Companion image instead of separate USB, BLE, +ordinary WiFi, and USB-logging images. On ESP32, logging is off by default so +the one USB TTY starts in ASCII and automatically changes to framed Companion +when a complete `<` frame arrives. Enabling logging gives that TTY to the +plaintext CLI/logger and disables framed USB Companion until logging is turned +off and the normal ASCII-to-binary mode switch occurs. nRF52 Full Companion +retains its optional dedicated interface `02`. Exact filename search still finds old aliases from earlier releases. diff --git a/docs/full_companion_usb_switcher.md b/docs/full_companion_usb_switcher.md index 36a86ee4..60ed649a 100644 --- a/docs/full_companion_usb_switcher.md +++ b/docs/full_companion_usb_switcher.md @@ -62,6 +62,7 @@ boot ------> ASCII terminal Binary Companion ASCII terminal -- +++MESHCORE-TERM-STOP ------> Binary Companion ASCII terminal -- observable USB disconnect ---> Binary Companion +ESP32 logging terminal -- usb.logging off -----> ASCII terminal any mode ------- reboot ------------------------> ASCII terminal ``` @@ -144,14 +145,21 @@ mOTA traffic on the same stream. | Situation | Primary USB behavior | | --- | --- | | Full Companion after boot | ASCII; a complete `<` frame switches to Binary Companion | -| Dual-CDC logging enabled | Primary interface still follows the switcher; logs use the optional second interface | -| Single-TTY logging enabled at boot | Logging terminal owns primary USB; automatic `<` detection is disabled | +| nRF52 logging enabled | Primary interface still follows the switcher; logs use the optional second interface | +| ESP32 logging enabled at boot | Logging terminal owns the only USB TTY; automatic `<` detection is disabled | | nRF52 USB serial mOTA active | mOTA owns primary USB; ASCII and Binary Companion are unavailable there | | BLE/WiFi/Ethernet/hardware serial | Always Binary Companion and unaffected by the USB mode | -On a single-TTY build, use `set usb.logging off` in its logging terminal before -trying to use primary USB with an app. BLE and WiFi Companion transports remain -available while primary USB is logging. +On every ESP32 Full Companion, use `set usb.logging off` in its logging terminal +before trying to use USB with an app. That command stops logging and returns to +the ordinary ASCII terminal; it does not select Binary Companion. The app's +first valid framed probe can perform the normal automatic switch, or a human +can send `+++MESHCORE-TERM-STOP` first. The framed/binary Companion parser is +disabled while logging owns that TTY. BLE and WiFi Companion transports remain +available while USB is logging. ESP32 Full builds use the Arduino-ESP32 2.x +base where supported; RC32 and ESP32-C6 keep their board-required Arduino 3.x +platform but follow the same one-TTY policy. No ESP32 Full build enumerates a +second CDC interface. On nRF52, serial `motatool` is also text-first. Its exact initial `ota folder on` line is recognized in either startup ASCII or Binary Companion @@ -235,7 +243,7 @@ prefix it with another character if it is needed as command text. If `meshcli` cannot connect: 1. close every terminal or logging reader using the primary interface; -2. confirm that saved single-TTY logging is off; +2. on ESP32, confirm that saved USB logging is off; 3. reboot and let `meshcli` be the first process to open the data interface; 4. use the stable `/dev/serial/by-id/*-if00` path on Linux when available; 5. if a prompt appears after one second, the client's first frame was not diff --git a/docs/terminal_chat_cli.md b/docs/terminal_chat_cli.md index dcc1a28f..38de32e9 100644 --- a/docs/terminal_chat_cli.md +++ b/docs/terminal_chat_cli.md @@ -120,10 +120,13 @@ set usb.logging {on|off} [reboot] Shows or changes persistent live USB debug and packet output in an ordinary USB-loggable Companion or Full Companion. USB Companion and Full start off on a fresh install with logging disabled to protect framed traffic. -Dual-CDC Full changes its interface count after a reboot and keeps Companion on -interface `00`. Single-TTY Full needs no reboot: logging uses the active text -terminal, which continues accepting `set usb.logging off` and automatically -restores Binary Companion after that reply. +nRF52 Full changes its interface count after a reboot and keeps Companion on +interface `00`. Every ESP32 Full Companion has one TTY and needs no reboot: +logging uses the active text terminal, disables framed Binary Companion on +USB, and continues accepting `set usb.logging off`. Turning logging off leaves +the port in the normal ASCII terminal, matching fresh firmware. Send +`+++MESHCORE-TERM-STOP`, or let a Companion app send a valid framed probe, to +switch it to Binary Companion afterward. ``` reboot diff --git a/examples/companion_radio/CompanionFeatures.h b/examples/companion_radio/CompanionFeatures.h index 6c854b99..0f162c7d 100644 --- a/examples/companion_radio/CompanionFeatures.h +++ b/examples/companion_radio/CompanionFeatures.h @@ -86,7 +86,7 @@ #if COMPANION_FEATURE_DEDICATED_USB_LOGGING \ && !(defined(MESH_DUAL_CDC_LOGGING) \ && defined(ENABLE_USB_INTERFACE)) - #error "COMPANION_FEATURE_DEDICATED_USB_LOGGING requires dual-CDC USB" + #error "COMPANION_FEATURE_DEDICATED_USB_LOGGING requires nRF52 dual-CDC USB" #endif #if defined(COMPANION_RADIO_FULL) && !defined(BLE_PIN_CODE) diff --git a/examples/companion_radio/MyMesh.cpp b/examples/companion_radio/MyMesh.cpp index 31fb25bf..fdefbbae 100644 --- a/examples/companion_radio/MyMesh.cpp +++ b/examples/companion_radio/MyMesh.cpp @@ -1500,7 +1500,7 @@ MyMesh::MyMesh(mesh::Radio &radio, mesh::RNG &rng, mesh::RTCClock &rtc, SimpleMe _prefs.cad_max_duration_ms = 0; #if defined(ENABLE_USB_INTERFACE) // Keep a USB Companion's primary stream exclusively framed on a fresh - // install. Full dual-CDC builds can add a diagnostics port; single-TTY + // install. nRF52 Full can add a diagnostics port; ESP32 and other single-TTY // builds switch the primary stream into the text terminal before logs. _prefs.usb_logging_enabled = 0; #else diff --git a/examples/companion_radio/main.cpp b/examples/companion_radio/main.cpp index 26f4b418..eac48daa 100644 --- a/examples/companion_radio/main.cpp +++ b/examples/companion_radio/main.cpp @@ -596,8 +596,8 @@ static void serviceUsbTerminal() { // logging repeater: plaintext diagnostics plus an input-capable CLI. Put the // Companion interface into passthrough before it can mix framed traffic with // logs. `set usb.logging off` remains available here and returns this TTY to - // Binary Companion after its command reply, even on USB-UART bridges that - // cannot detect a host disconnect. + // the normal ASCII terminal, matching a fresh Full install. Binary Companion + // starts only after the usual terminal-stop token or framed startup probe. #if MESH_USB_LOGGING_AVAILABLE if (!mesh::hasDedicatedUsbLoggingPort()) { if (mesh::isUsbLoggingEnabled()) { @@ -607,8 +607,15 @@ static void serviceUsbTerminal() { } usb_logging_terminal_mode = true; } else if (usb_logging_terminal_mode && the_mesh.isTerminalMode()) { - leaveUsbTerminalMode(true); - return; + // Logging may also be disabled over BLE/WiFi. Stop treating this session + // as the logging terminal, but keep the ordinary ASCII terminal active; + // do not silently change the USB protocol underneath an idle host. A + // remote mode change also cancels any partially typed USB command before + // drawing a fresh prompt. + usb_logging_terminal_mode = false; + clearUsbTerminalLine(); + usb_terminal_discard_line = false; + Serial.print("\r\nUSB logging off; ASCII terminal active\r\n> "); } } #endif @@ -698,8 +705,10 @@ static void serviceUsbTerminal() { #if MESH_USB_LOGGING_AVAILABLE if (usb_logging_terminal_mode && !mesh::isUsbLoggingEnabled()) { - leaveUsbTerminalMode(true); - return; + // The command reply belongs to the ASCII session. Leave that session + // active so a separate, observable mode switch is required before the + // port accepts framed Binary Companion traffic again. + usb_logging_terminal_mode = false; } #endif Serial.print("> "); @@ -1422,8 +1431,8 @@ void setup() { #endif // nRF52 cannot decide whether to add its optional logging CDC interface - // until the saved Companion preferences above are available. ESP32 already - // fixed its descriptor from the early NVS mirror, so this is harmless there. + // until the saved Companion preferences above are available. Single-TTY + // platforms have no separate port, so this is a harmless no-op there. mesh::beginUsbLoggingPort(); // Load WiFi state before bringing up either wireless stack. @@ -1576,8 +1585,8 @@ void loop() { #if defined(NRF52_PLATFORM) board.feedWatchdog(); #endif - // Identify CDC 1 when a terminal opens it. Doing this on the connection edge - // avoids losing the marker during boot before the host has opened the port. + // Identify nRF52 CDC 1 when a terminal opens it. Doing this on the connection + // edge avoids losing the marker before the host has opened the port. mesh::serviceUsbLoggingPort(); #if defined(ENABLE_USB_INTERFACE) && defined(COMPANION_RADIO_FULL) expireUsbBinaryStartupProbeBeforeDispatch(); diff --git a/mesh-america/update-logging-provider-release.py b/mesh-america/update-logging-provider-release.py index 3e7fab90..0676c5d8 100755 --- a/mesh-america/update-logging-provider-release.py +++ b/mesh-america/update-logging-provider-release.py @@ -141,9 +141,10 @@ def update_catalog(catalog: dict, release_files: dict[str, list[Path]], args: ar "provide USB packet logging and direct WiFi MQTT in one image with a " "persistent off/USB/WiFi/both selector. Host software can consume the " "USB serial log and publish it separately. Full Companion replaces " - "older USB-logging images. Dual-CDC builds add a separate plaintext " - "interface after reboot; single-TTY builds use an input-capable " - "logging terminal on their existing port. " + "older USB-logging images. nRF52 Full Companion can add a separate " + "plaintext interface after reboot; ESP32 Full Companion uses its " + "one USB TTY as an input-capable logging terminal and disables " + "framed USB Companion while logging. " "Open Release notes for role, hardware, installation, and partition " "requirements." ) @@ -279,19 +280,11 @@ def update_catalog(catalog: dict, release_files: dict[str, list[Path]], args: ar notes = common.normalize_nrf52_full_companion_metadata( firmware, notes ) - elif device_type == "esp32" and any( - common.ESP32_DUAL_CDC_FULL_RE.match(identity) - is not None - for identity in identities - ): - notes = common.normalize_esp32_dual_cdc_full_companion_metadata( - firmware, notes - ) elif device_type == "esp32" and any( "companion_radio_full" in identity.lower() for identity in identities ): - notes = common.normalize_esp32_single_tty_full_companion_metadata( + notes = common.normalize_esp32_full_companion_metadata( firmware, notes ) if any("-logging" in identity.lower() for identity in identities): diff --git a/mesh-america/update-provider-release.py b/mesh-america/update-provider-release.py index 3e2eb9c6..a93b2c05 100644 --- a/mesh-america/update-provider-release.py +++ b/mesh-america/update-provider-release.py @@ -19,16 +19,6 @@ VERSION_LABEL_RE = re.compile(r"v\d+(?:\.\d+)+") CATALOG_VERSION_TEXT_RE = re.compile( r"Keymind Cascade MeshCore v[0-9][A-Za-z0-9_.-]*" ) -ESP32_DUAL_CDC_FULL_RE = re.compile( - r"^(?:" - r"heltec_v4(?:_2_v4_3|_3)?(?:_r8)?(?:_tft)?|" - r"LilyGo_TBeam_1W|Station_G2|Station_G3_ESP32|Xiao_S3_WIO|" - r"heltec_tracker_v2|meshnology_w12|" - r"nibble_(?:screen|zero)_connect" - r")_companion_radio_full(?:_|$)", - flags=re.IGNORECASE, -) - LEGACY_PORTABLE_CEILING_EXCEPTIONS = { "heltec_ct62_repeater_lora_ota_no_external_sensors", "heltec_tracker_v1_1_repeater_observer_mqtt", @@ -344,78 +334,25 @@ def normalize_nrf52_full_companion_metadata( return "\n\n".join(paragraphs) -def normalize_esp32_dual_cdc_full_companion_metadata( +def normalize_esp32_full_companion_metadata( firmware: dict, notes: str ) -> str: - """Describe a native-USB ESP32-S3 Full Companion accurately.""" + """Describe the canonical single-TTY ESP32 Full Companion accurately.""" firmware["title"] = "Full Companion" firmware["subTitle"] = ( - "USB Companion + optional USB logging + BLE + Wi-Fi + LoRa OTA source" + "USB Companion or logging + BLE + Wi-Fi + LoRa OTA source" ) profile = ( - "PROFILE - Native-USB ESP32-S3 Full Companion: one USB cable always " - "exposes interface 00 for Binary Companion, the text terminal, flashing, and " - "serial mOTA source traffic. Fresh installs default USB logging off and " - "do not enumerate interface 02. Enabling logging and rebooting adds " - "interface 02 as a separate plaintext packet/debug port. BLE, Wi-Fi " - "Companion on TCP 5000, " - "WebConfig, TCP mOTA seeding on 5001, and the text terminal on 5002 " - "remain available. Use set usb.logging on reboot or set usb.logging " - "off reboot to save and apply the interface count." - ) - logging_use = ( - "LOGGING USE - Point Companion software, meshcli, motatool, and wired " - "flashing at USB interface 00. After enabling logging and rebooting, " - "point a USB-connected MQTT/logging reader at interface 02. Match the " - "USB interface number rather than assuming a tty or COM port number. " - "Input received on interface 02 is ignored and cannot reboot the board." - ) - selection = ( - "SELECTION - One Full image for this exact native-USB hardware layout " - "replaces separate USB, BLE, ordinary Wi-Fi, and USB-logging images." - ) - - paragraphs: list[str] = [] - profile_added = False - selection_added = False - for paragraph in notes.split("\n\n"): - if paragraph.startswith("PROFILE "): - if not profile_added: - paragraphs.append(profile) - profile_added = True - continue - if paragraph.startswith("LOGGING USE "): - continue - if paragraph.startswith("SELECTION "): - if not selection_added: - paragraphs.append(selection) - selection_added = True - continue - paragraphs.append(paragraph) - if not profile_added: - paragraphs.append(profile) - if not selection_added: - paragraphs.append(selection) - paragraphs.append(logging_use) - return "\n\n".join(paragraphs) - - -def normalize_esp32_single_tty_full_companion_metadata( - firmware: dict, notes: str -) -> str: - """Describe an ESP32 Full Companion whose USB path has one TTY.""" - firmware["title"] = "Full Companion" - firmware["subTitle"] = ( - "USB Companion/logging + BLE + Wi-Fi + LoRa OTA source" - ) - profile = ( - "PROFILE - Single-TTY ESP32 Full Companion: the USB TTY defaults to " - "Binary Companion on a fresh install. BLE, Wi-Fi Companion on TCP " + "PROFILE - ESP32 Full Companion: its single USB TTY starts with USB " + "logging off and uses the ASCII/Binary Companion switcher. BLE, " + "Wi-Fi Companion on TCP " "5000, WebConfig, TCP mOTA seeding on 5001, and the text terminal on " "5002 remain available. Enter the USB text terminal and use set " "usb.logging on to turn that TTY into an input-capable plaintext " "packet/debug stream. Use set usb.logging off to stop diagnostics; " - "after its reply, the TTY returns to Binary Companion automatically. " + "after its reply, the TTY remains in the normal ASCII terminal, matching " + "fresh Full firmware. Then use the normal terminal stop token or let a " + "Companion app send a valid framed probe to select Binary Companion. " "The saved logging choice is restored at boot." ) logging_use = ( @@ -483,9 +420,9 @@ def resolve_release_identity( if candidate in release_files: return candidate, False - # Full Companion replaces the old USB-only logging artifact. Dual-CDC - # hardware uses its second interface; single-TTY hardware safely switches - # its primary port into an input-capable plaintext logging terminal. + # Full Companion replaces the old USB-only logging artifact. ESP32 safely + # switches its one TTY between framed Companion and an input-capable + # plaintext logging terminal; nRF52 can expose a second interface. for candidate in candidates: if not candidate.endswith("-logging"): continue @@ -777,9 +714,9 @@ def update_catalog(catalog: dict, release_files: dict[str, list[Path]], args: ar "USB + Wi-Fi observer and ESP-NOW bridge builds. Unified observers " "provide persistent off/USB/WiFi/both output selection. Full Companion " "replaces separate BLE, USB, ordinary Wi-Fi, and USB-logging choices " - "with one image. Dual-CDC builds can add a separate plaintext port; " - "single-TTY builds switch that port into an input-capable logging " - "terminal. Open Release notes for role, " + "with one image. nRF52 Full Companion can add a separate plaintext " + "port; ESP32 Full Companion switches its one USB TTY into an " + "input-capable logging terminal. Open Release notes for role, " "hardware, installation, and partition requirements." ) @@ -874,18 +811,11 @@ def update_catalog(catalog: dict, release_files: dict[str, list[Path]], args: ar notes = normalize_nrf52_full_companion_metadata( firmware, notes ) - elif device_type == "esp32" and any( - ESP32_DUAL_CDC_FULL_RE.match(identity) is not None - for identity in resolved_identities - ): - notes = normalize_esp32_dual_cdc_full_companion_metadata( - firmware, notes - ) elif device_type == "esp32" and any( "companion_radio_full" in identity.lower() for identity in resolved_identities ): - notes = normalize_esp32_single_tty_full_companion_metadata( + notes = normalize_esp32_full_companion_metadata( firmware, notes ) diff --git a/platformio.ini b/platformio.ini index be3b151d..882b5fc9 100644 --- a/platformio.ini +++ b/platformio.ini @@ -111,28 +111,6 @@ platform = https://github.com/pioarduino/platform-espressif32/releases/download/ [esp32_s3_full] board_build.flash_mode = dio -; Full Companion profiles on boards whose connector is wired to the ESP32-S3 -; native USB peripheral use Arduino-ESP32 3.x TinyUSB. This supplies two real -; CDC ACM interfaces: CDC 0 for framed Companion traffic and optional CDC 1 -; for plaintext diagnostics. Do not apply this section to USB-UART-bridge -; boards; firmware cannot add an interface to an external bridge chip. -[esp32_s3_dual_cdc_full] -platform = https://github.com/pioarduino/platform-espressif32/releases/download/55.03.311/platform-espressif32.zip -; Pin the matching Arduino core as an explicit project dependency. Standard -; ESP32 profiles use Arduino 2.x under the same PlatformIO package name, so an -; alternating build otherwise can leave this Arduino 3.x profile resolving the -; wrong global framework directory (or no compatible directory at all). -platform_packages = - framework-arduinoespressif32 @ https://github.com/espressif/arduino-esp32/releases/download/3.3.11/esp32-core-3.3.11.tar.xz - framework-arduinoespressif32-libs @ https://github.com/espressif/arduino-esp32/releases/download/3.3.11/esp32-core-3.3.11-libs.tar.xz -board_build.flash_mode = ${esp32_s3_full.board_build.flash_mode} -build_unflags = - -D ARDUINO_USB_MODE=1 - -D ARDUINO_USB_CDC_ON_BOOT=0 -build_flags = - -D ARDUINO_USB_MODE=0 - -D ARDUINO_USB_CDC_ON_BOOT=1 - ; ----------------- NRF52 --------------------- [nrf52_base] diff --git a/scripts/esp32_full_partition.py b/scripts/esp32_full_partition.py index d891da87..20910d28 100644 --- a/scripts/esp32_full_partition.py +++ b/scripts/esp32_full_partition.py @@ -15,6 +15,13 @@ def parse_flash_size(value): if os.environ.get("MESHCORE_ESP32_FULL_BUILD") == "1": board = env.BoardConfig() flash_size = parse_flash_size(board.get("upload.flash_size", "4MB")) + if str(board.get("build.mcu", "")).lower() == "esp32s3": + # Every S3 Full image must remain ROM-bootable across the supported + # flash parts. Some board manifests default to QIO even though their + # ROM/bootloader chain only starts a merged image reliably in DIO. + # PSRAM mode is an independent setting and is intentionally untouched. + board.update("build.flash_mode", "dio") + print("ESP32-S3 FULL build: forcing ROM-compatible DIO flash mode") companion_radio_full = ( os.environ.get("MESHCORE_COMPANION_RADIO_FULL") == "1" ) diff --git a/src/helpers/CommonCLI.cpp b/src/helpers/CommonCLI.cpp index 26c97695..f9681602 100644 --- a/src/helpers/CommonCLI.cpp +++ b/src/helpers/CommonCLI.cpp @@ -6,6 +6,7 @@ #include "TxtDataHelpers.h" #include "AdvertDataHelpers.h" #include "AlertReporter.h" // for alertReporterBannedChannelMatch() +#include "sensors/EnvironmentI2CConfig.h" #if defined(NRF52_PLATFORM) #include "AtomicFileWriter.h" #include "ota/OtaFlashLayout_nrf52.h" @@ -2785,7 +2786,7 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, char* command, char* re } } else if (memcmp(command, "sensor", 6) == 0) { // I2C -#if defined(ENV_PIN_SDA) && defined(ENV_PIN_SCL) +#if ENV_HAS_SECONDARY_I2C sprintf(reply, "I2C Wire1: SDA=%s,SCL=%s\r\n", STR(ENV_PIN_SDA), STR(ENV_PIN_SCL)); #elif defined(PIN_BOARD_SDA) && defined(PIN_BOARD_SCL) sprintf(reply, "I2C Wire: SDA=%s, SCL=%s\r\n", STR(PIN_BOARD_SDA), STR(PIN_BOARD_SCL)); diff --git a/src/helpers/UsbLogging.cpp b/src/helpers/UsbLogging.cpp index 6935c533..4cec1d7f 100644 --- a/src/helpers/UsbLogging.cpp +++ b/src/helpers/UsbLogging.cpp @@ -3,32 +3,18 @@ #if defined(ARDUINO) #include #include -#include #if defined(MESH_DUAL_CDC_LOGGING) #if !defined(COMPANION_FEATURE_DEDICATED_USB_LOGGING) || \ !COMPANION_FEATURE_DEDICATED_USB_LOGGING #error "MESH_DUAL_CDC_LOGGING requires its dedicated Companion capability" #endif - #if defined(NRF52_PLATFORM) - #define MESH_NRF52_DUAL_CDC_LOGGING 1 - #include - #if !defined(CFG_TUD_CDC) || CFG_TUD_CDC < 2 - #error "MESH_DUAL_CDC_LOGGING requires CFG_TUD_CDC >= 2" - #endif - #elif defined(ESP32_PLATFORM) && defined(SOC_USB_OTG_SUPPORTED) && \ - SOC_USB_OTG_SUPPORTED && !ARDUINO_USB_MODE - #define MESH_ESP32_DUAL_CDC_LOGGING 1 - #include - #include - #include - #include - #if !defined(CONFIG_TINYUSB_CDC_MAX_PORTS) || \ - CONFIG_TINYUSB_CDC_MAX_PORTS < 2 - #error "MESH_DUAL_CDC_LOGGING requires two ESP32 TinyUSB CDC ports" - #endif - #else - #error "MESH_DUAL_CDC_LOGGING requires nRF52 or ESP32 native TinyUSB" + #if !defined(NRF52_PLATFORM) + #error "MESH_DUAL_CDC_LOGGING is supported only by nRF52 Full Companion" + #endif + #include + #if !defined(CFG_TUD_CDC) || CFG_TUD_CDC < 2 + #error "MESH_DUAL_CDC_LOGGING requires CFG_TUD_CDC >= 2" #endif #endif @@ -60,68 +46,7 @@ static NullUsbLoggingStream null_usb_logging_stream; static bool dedicated_usb_logging_port_configured = false; static bool dedicated_usb_logging_port_started = false; static bool dedicated_usb_logging_port_connected = false; -#if defined(MESH_NRF52_DUAL_CDC_LOGGING) static Adafruit_USBD_CDC dedicated_usb_logging_port; -#elif defined(MESH_ESP32_DUAL_CDC_LOGGING) -static constexpr char USB_LOGGING_NVS_NAMESPACE[] = "mesh_usb"; -static constexpr char USB_LOGGING_NVS_KEY[] = "log_port"; -alignas(USBCDC) static uint8_t dedicated_usb_logging_port_storage[ - sizeof(USBCDC)]; -static USBCDC* dedicated_usb_logging_port = nullptr; - -static bool readEsp32UsbLoggingBootPreference() { - // Missing or unreadable state must stay protocol-safe: Full Companion then - // exposes only its primary framed CDC interface. The normal preference load - // below mirrors an explicitly enabled setting for the following reboot. - if (nvs_flash_init() != ESP_OK) return false; - - nvs_handle_t handle; - if (nvs_open(USB_LOGGING_NVS_NAMESPACE, NVS_READONLY, &handle) != ESP_OK) { - return false; - } - - uint8_t value = 0; - const esp_err_t result = nvs_get_u8(handle, USB_LOGGING_NVS_KEY, &value); - nvs_close(handle); - return result == ESP_OK && value != 0; -} - -static bool writeEsp32UsbLoggingBootPreference(bool enabled) { - if (nvs_flash_init() != ESP_OK) return false; - - nvs_handle_t handle; - if (nvs_open(USB_LOGGING_NVS_NAMESPACE, NVS_READWRITE, &handle) != ESP_OK) { - return false; - } - - uint8_t current = 0; - esp_err_t result = nvs_get_u8(handle, USB_LOGGING_NVS_KEY, ¤t); - if (result == ESP_OK && current == (enabled ? 1 : 0)) { - nvs_close(handle); - return true; - } - result = nvs_set_u8(handle, USB_LOGGING_NVS_KEY, enabled ? 1 : 0); - if (result == ESP_OK) result = nvs_commit(handle); - nvs_close(handle); - return result == ESP_OK; -} - -class Esp32UsbLoggingBootstrap { - public: - Esp32UsbLoggingBootstrap() { - const bool enabled = readEsp32UsbLoggingBootPreference(); - usb_logging_enabled.store(enabled, std::memory_order_relaxed); - usb_logging_preference_known.store(true, std::memory_order_relaxed); - if (!enabled) return; - - dedicated_usb_logging_port = new (dedicated_usb_logging_port_storage) - USBCDC(1); - dedicated_usb_logging_port_configured = true; - } -}; - -static Esp32UsbLoggingBootstrap esp32_usb_logging_bootstrap; -#endif #endif bool isUsbLoggingEnabled() { @@ -134,12 +59,8 @@ void setUsbLoggingEnabled(bool enabled) { } bool saveUsbLoggingBootPreference(bool enabled) { -#if defined(MESH_ESP32_DUAL_CDC_LOGGING) - return writeEsp32UsbLoggingBootPreference(enabled); -#else (void)enabled; return true; -#endif } void beginUsbLoggingPort() { @@ -150,27 +71,13 @@ void beginUsbLoggingPort() { return; } -#if defined(MESH_ESP32_DUAL_CDC_LOGGING) - // ESP32 registers its descriptor before app_main starts native USB. If the - // saved boot mirror omitted CDC 1, enabling logging takes effect after the - // next reboot instead of trying to mutate a live USB descriptor. - if (dedicated_usb_logging_port == nullptr) return; - // Only the Companion interface may use the CDC control-line reboot gesture. - // A terminal opening or closing the diagnostics interface must never reboot - // the node or put it into the ROM downloader. - dedicated_usb_logging_port->enableReboot(false); - dedicated_usb_logging_port->setTxTimeoutMs(0); - dedicated_usb_logging_port->begin(115200); -#elif defined(MESH_NRF52_DUAL_CDC_LOGGING) dedicated_usb_logging_port.begin(115200); // `begin()` installs the core's generic "TinyUSB Serial" name. Replace it // before (re-)enumeration so host USB tools can distinguish the log endpoint. dedicated_usb_logging_port.setStringDescriptor("MeshCore Logging"); dedicated_usb_logging_port_configured = true; -#endif dedicated_usb_logging_port_started = true; -#if defined(MESH_NRF52_DUAL_CDC_LOGGING) // The nRF52 core starts TinyUSB before setup(). If the host completed // enumeration in that small window, reconnect once so it reads the expanded // two-CDC descriptor. Usually enumeration has not completed and no reconnect @@ -181,20 +88,13 @@ void beginUsbLoggingPort() { TinyUSBDevice.attach(); } #endif -#endif } void serviceUsbLoggingPort() { #if defined(MESH_DUAL_CDC_LOGGING) bool connected = false; -#if defined(MESH_NRF52_DUAL_CDC_LOGGING) connected = dedicated_usb_logging_port_started && dedicated_usb_logging_port.dtr(); -#elif defined(MESH_ESP32_DUAL_CDC_LOGGING) - connected = dedicated_usb_logging_port_started - && dedicated_usb_logging_port != nullptr - && (bool)*dedicated_usb_logging_port; -#endif if (connected && !dedicated_usb_logging_port_connected && isUsbLoggingEnabled()) { @@ -208,16 +108,9 @@ void serviceUsbLoggingPort() { Stream& usbLoggingPort() { #if defined(MESH_DUAL_CDC_LOGGING) -#if defined(MESH_NRF52_DUAL_CDC_LOGGING) if (isUsbLoggingEnabled() && dedicated_usb_logging_port_started) { return dedicated_usb_logging_port; } -#elif defined(MESH_ESP32_DUAL_CDC_LOGGING) - if (isUsbLoggingEnabled() && dedicated_usb_logging_port_started - && dedicated_usb_logging_port != nullptr) { - return *dedicated_usb_logging_port; - } -#endif return null_usb_logging_stream; #else if (!isUsbLoggingEnabled()) return null_usb_logging_stream; diff --git a/src/helpers/UsbLogging.h b/src/helpers/UsbLogging.h index 35b8cdac..c2e308da 100644 --- a/src/helpers/UsbLogging.h +++ b/src/helpers/UsbLogging.h @@ -21,13 +21,13 @@ namespace mesh { bool isUsbLoggingEnabled(); void setUsbLoggingEnabled(bool enabled); -// ESP32 native USB starts before setup(), so its next-boot interface count is -// mirrored outside the normal role preferences. Other platforms need no -// mirror. Returns false only when that mirror could not be saved. +// Kept as a platform-neutral persistence hook. Single-TTY builds need no +// descriptor mirror; nRF52 selects its optional second interface after loading +// the normal role preferences. bool saveUsbLoggingBootPreference(bool enabled); // Start the optional dedicated USB logging interface. Ordinary and single-TTY -// builds use Serial; dual-CDC Full Companion builds use a second CDC ACM port. +// builds use Serial; nRF52 Full Companion uses a second CDC ACM port. void beginUsbLoggingPort(); // Emit a short identity marker whenever a host opens the dedicated logging // endpoint. The device can report its USB interface, but the host alone chooses diff --git a/src/helpers/UserGpioPinPolicy.h b/src/helpers/UserGpioPinPolicy.h index 1cce2e42..505708e3 100644 --- a/src/helpers/UserGpioPinPolicy.h +++ b/src/helpers/UserGpioPinPolicy.h @@ -3,6 +3,8 @@ #include #include +#include "sensors/EnvironmentI2CConfig.h" + // Simulator targets do not include RadioLib, but board pin macros can still // use its sentinel value. Keep the fallback local to this header. #ifndef RADIOLIB_NC @@ -150,10 +152,8 @@ inline bool isFirmwareReserved(uint8_t pin) { #ifdef PIN_WIRE_SCL PIN_WIRE_SCL, #endif -#ifdef ENV_PIN_SDA +#if ENV_HAS_SECONDARY_I2C ENV_PIN_SDA, -#endif -#ifdef ENV_PIN_SCL ENV_PIN_SCL, #endif #ifdef I2C_SDA diff --git a/src/helpers/bridges/MQTTBridge.cpp b/src/helpers/bridges/MQTTBridge.cpp index 3cdb9b2b..8c79c0d5 100644 --- a/src/helpers/bridges/MQTTBridge.cpp +++ b/src/helpers/bridges/MQTTBridge.cpp @@ -1292,7 +1292,11 @@ void MQTTBridge::initializeWiFiInTask() { // Enable automatic reconnection - ESP32 will handle reconnection automatically WiFi.setAutoReconnect(true); +#if !defined(ESP_ARDUINO_VERSION_MAJOR) || ESP_ARDUINO_VERSION_MAJOR < 3 + // Arduino-ESP32 3.x removed setAutoConnect(); begin() plus + // setAutoReconnect() is the supported equivalent there. WiFi.setAutoConnect(true); +#endif // Set up WiFi event handlers for better diagnostics and immediate disconnection // detection. Register ONCE - the bridge is reused across restarts (e.g. stopped @@ -4098,7 +4102,9 @@ bool MQTTBridge::syncTimeWithNTP(bool force, bool primary_only) { for (int s = 0; s < server_count && !ntp_ok; s++) { const char* server = servers[s]; MQTT_DEBUG_PRINTLN("SNTP fallback trying %s...", server); - if (sntp_enabled()) sntp_stop(); + // configTime() already stops and reconfigures an active SNTP client on + // both Arduino-ESP32 2.x and 3.x. Avoid the legacy unprefixed stop APIs, + // which are no longer exported by esp_sntp.h in Arduino 3.x. sntp_set_sync_status(SNTP_SYNC_STATUS_RESET); configTime(0, 0, server); for (int i = 0; i < 20; i++) { diff --git a/src/helpers/sensors/EnvironmentI2CConfig.h b/src/helpers/sensors/EnvironmentI2CConfig.h new file mode 100644 index 00000000..5c5e0b79 --- /dev/null +++ b/src/helpers/sensors/EnvironmentI2CConfig.h @@ -0,0 +1,11 @@ +#pragma once + +// ENV_PIN_SDA / ENV_PIN_SCL select a dedicated environmental-sensor bus. +// GPIO 0 is valid on supported targets, while negative values are the common +// disconnected-pin sentinel. Keep every consumer on the same validity rule. +#if defined(ENV_PIN_SDA) && defined(ENV_PIN_SCL) \ + && (ENV_PIN_SDA >= 0) && (ENV_PIN_SCL >= 0) +#define ENV_HAS_SECONDARY_I2C 1 +#else +#define ENV_HAS_SECONDARY_I2C 0 +#endif diff --git a/src/helpers/sensors/EnvironmentSensorManager.cpp b/src/helpers/sensors/EnvironmentSensorManager.cpp index b32025bd..b0e61730 100644 --- a/src/helpers/sensors/EnvironmentSensorManager.cpp +++ b/src/helpers/sensors/EnvironmentSensorManager.cpp @@ -1,8 +1,9 @@ #include "EnvironmentSensorManager.h" +#include "EnvironmentI2CConfig.h" #include -#if ENV_PIN_SDA && ENV_PIN_SCL +#if ENV_HAS_SECONDARY_I2C #define TELEM_WIRE &Wire1 // Use Wire1 as the I2C bus for Environment Sensors #else #define TELEM_WIRE &Wire // Use default I2C bus for Environment Sensors @@ -707,7 +708,7 @@ bool EnvironmentSensorManager::begin() { #endif #endif - #if ENV_PIN_SDA && ENV_PIN_SCL + #if ENV_HAS_SECONDARY_I2C #ifdef NRF52_PLATFORM Wire1.setPins(ENV_PIN_SDA, ENV_PIN_SCL); Wire1.setClock(100000); diff --git a/test/test_build_profiles.sh b/test/test_build_profiles.sh index b541dbac..252afae2 100644 --- a/test/test_build_profiles.sh +++ b/test/test_build_profiles.sh @@ -106,6 +106,61 @@ require("Heltec_E290_companion_usb_ble", "build_flags", "ENABLE_USB_INTERFACE") require("Heltec_E290_companion_usb_ble", "build_flags", "BLE_PIN_CODE=123456") require("Heltec_T190_companion_radio_usb_ble_", "build_flags", "ENABLE_USB_INTERFACE") require("Heltec_T190_companion_radio_usb_ble_", "build_flags", "BLE_PIN_CODE=123456") +require("heltec_v4_expansionkit_tft_companion_radio_full_femon", "platform", "platformio/espressif32@6.11.0") +require("heltec_v4_expansionkit_tft_companion_radio_full_femon", "build_flags", "ARDUINO_USB_MODE=1") +reject("heltec_v4_expansionkit_tft_companion_radio_full_femon", "build_flags", "ARDUINO_USB_MODE=0") +reject("heltec_v4_expansionkit_tft_companion_radio_full_femon", "platform", "55.03.311") +reject("heltec_v4_expansionkit_tft_companion_radio_full_femon", "platform_packages", "esp32-core-3.3.11") + +# ESP32 Full uses the board-specific Arduino-ESP32 2.x recipe and one USB TTY. The +# independently qualified RC32 stays on Arduino 3.x, but it must not enable a +# second CDC interface either. +for env_name in ( + "LilyGo_TBeam_1W_companion_radio_full", + "RAK_3112_companion_radio_full", + "Station_G2_companion_radio_full", + "ThinkNode_M2_companion_radio_full", + "ThinkNode_M5_companion_radio_full", + "ThinkNode_M7_companion_radio_full", + "Xiao_S3_WIO_companion_radio_full", + "heltec_v4_r8_companion_radio_full", + "heltec_v4_r8_tft_companion_radio_full", + "meshnology_w12_companion_radio_full", + "heltec_v4_2_v4_3_companion_radio_full_femon", + "heltec_v4_3_companion_radio_full_femoff", + "heltec_v4_expansionkit_tft_companion_radio_full_femon", + "heltec_v4_tft_companion_radio_full_femon", + "heltec_v4_3_tft_companion_radio_full_femoff", + "nibble_zero_connect_companion_radio_full_", + "nibble_screen_connect_companion_radio_full_", + "Station_G3_ESP32_companion_radio_full", + "Heltec_v3_companion_radio_full", + "heltec_tracker_v2_companion_radio_full_femon", +): + require(env_name, "platform", "platformio/espressif32@6.11.0") + reject(env_name, "platform", "55.03.311") + reject(env_name, "build_flags", "MESH_DUAL_CDC_LOGGING") + reject(env_name, "build_flags", "COMPANION_FEATURE_DEDICATED_USB_LOGGING") + reject(env_name, "build_flags", "CFG_TUD_CDC=2") + +for env_name in ( + "heltec_rc32_without_display_companion_radio_full", + "heltec_rc32_companion_radio_full", +): + require(env_name, "platform", "55.03.311") + reject(env_name, "build_flags", "MESH_DUAL_CDC_LOGGING") + reject(env_name, "build_flags", "COMPANION_FEATURE_DEDICATED_USB_LOGGING") + reject(env_name, "build_flags", "CFG_TUD_CDC=2") + +# V3/V4 Full are the one canonical Companion image for each base OLED layout, +# including the former separately published direct-WiFi-MQTT capability. +for env_name in ( + "Heltec_v3_companion_radio_full", + "heltec_v4_2_v4_3_companion_radio_full_femon", +): + require(env_name, "build_flags", "WITH_MQTT_BRIDGE=1") + require(env_name, "build_src_filter", "helpers/bridges/MQTTBridge.cpp") + require(env_name, "lib_deps", "PsychicMqttClient") rc32_repeater = "heltec_rc32_repeater" require(rc32_repeater, "platform", "55.03.311") @@ -115,6 +170,55 @@ require(rc32_repeater, "build_flags", "RC32_PERIPHERAL_WARMUP_MS=100") require(rc32_repeater, "build_flags", "SX126X_ALLOW_RECOVERABLE_INIT_STATUS=1") reject(rc32_repeater, "build_flags", "ESP32_CPU_FREQ=160") +# Environmental telemetry must follow the verified connector bus without +# creating a second Wire instance on the same pins as the board bus. +require("Heltec_t096_companion_radio_usb_femon", "build_flags", "ENV_PIN_SDA=PIN_WIRE1_SDA") +require("Heltec_t096_companion_radio_usb_femon", "build_flags", "ENV_PIN_SCL=PIN_WIRE1_SCL") +require("Heltec_v3_companion_radio_wifi", "build_flags", "ENV_PIN_SDA=33") +require("Heltec_v3_companion_radio_wifi", "build_flags", "ENV_PIN_SCL=34") + +for env_name in ( + "heltec_rc32_without_display_sensor", + "heltec_rc32_sensor", +): + require(env_name, "build_flags", "PIN_BOARD_SDA=21") + require(env_name, "build_flags", "PIN_BOARD_SCL=18") + reject(env_name, "build_flags", "ENV_PIN_") + +require("meshnology_w12_sensor", "build_flags", "PIN_BOARD_SDA=17") +require("meshnology_w12_sensor", "build_flags", "PIN_BOARD_SCL=18") +reject("meshnology_w12_sensor", "build_flags", "ENV_PIN_") + +for env_name in ( + "heltec_v4_sensor", + "heltec_v4_2_v4_3_companion_radio_full_femon", +): + require(env_name, "build_flags", "PIN_BOARD_SDA=17") + require(env_name, "build_flags", "PIN_BOARD_SCL=18") + require(env_name, "build_flags", "ENV_PIN_SDA=4") + require(env_name, "build_flags", "ENV_PIN_SCL=3") + +for env_name in ( + "heltec_v4_tft_sensor", + "heltec_v4_expansionkit_tft_companion_radio_ble_femon", + "heltec_v4_expansionkit_tft_companion_radio_full_femon", + "heltec_v4_tft_companion_radio_full_femon", +): + require(env_name, "build_flags", "PIN_BOARD_SDA=4") + require(env_name, "build_flags", "PIN_BOARD_SCL=3") + reject(env_name, "build_flags", "ENV_PIN_") + +for env_name in ( + "RAK_3112_sensor", + "RAK_3112_companion_radio_full", +): + require(env_name, "build_flags", "PIN_BOARD_SDA=9") + require(env_name, "build_flags", "PIN_BOARD_SCL=40") + require(env_name, "build_flags", "ENV_PIN_SDA=17") + require(env_name, "build_flags", "ENV_PIN_SCL=18") + reject(env_name, "build_flags", "ENV_PIN_SDA=33") + reject(env_name, "build_flags", "ENV_PIN_SCL=34") + for env_name in ( "Heltec_t114_without_display_repeater", "Heltec_t114_repeater", @@ -159,6 +263,72 @@ require(rak_usb, "build_flags", "RAK_BOARD") require(rak_usb, "build_flags", "FORCE_GPS_ALIVE") ' +# Every qualified ESP32 Companion layout publishes one Full image even when +# its historical recipe had no separately named WiFi target. The transport +# aliases remain directly buildable, but canonical release resolution must +# replace each with that exact board's Full target. +init_project_context >/dev/null +while IFS='|' read -r source_env full_env build_base; do + [ "${PIO_ENV_PLATFORM_BY_NAME[$full_env]:-}" = ESP32_PLATFORM ] \ + || fail "$full_env was not registered as ESP32 Full Companion" + [ "$(get_pio_build_env "$full_env")" = "$build_base" ] \ + || fail "$full_env did not retain its exact board recipe" + [ "$(get_esp32_full_companion_replacement "$source_env")" = "$full_env" ] \ + || fail "$source_env did not map to $full_env" + is_redundant_bulk_build_target "$source_env" \ + || fail "$source_env remained beside its Full Companion" +done <<'FULL_COMPANION_SPECS' +LilyGo_Tlora_C6_companion_radio_ble_|LilyGo_Tlora_C6_companion_radio_full_|LilyGo_Tlora_C6_companion_radio_ble_ +Meshimi_companion_radio_ble_|Meshimi_companion_radio_full_|Meshimi_companion_radio_ble_ +WHY2025_badge_companion_radio_ble_|WHY2025_badge_companion_radio_full_|WHY2025_badge_companion_radio_ble_ +Xiao_C6_companion_radio_ble_|Xiao_C6_companion_radio_full_|Xiao_C6_companion_radio_ble_ +heltec_v4_3_expansionkit_tft_companion_radio_ble_femoff|heltec_v4_expansionkit_tft_companion_radio_full_femon|heltec_v4_expansionkit_tft_companion_radio_full_femon +Generic_ESPNOW_comp_radio_usb|Generic_ESPNOW_companion_radio_full|Generic_ESPNOW_comp_radio_usb +Heltec_E290_companion_usb_ble|Heltec_E290_companion_radio_full|Heltec_E290_companion_usb_ble +Heltec_T190_companion_radio_usb_ble_|Heltec_T190_companion_radio_full_|Heltec_T190_companion_radio_usb_ble_ +SenseCapIndicator-ESPNow_comp_radio_usb|SenseCapIndicator-ESPNow_companion_radio_full|SenseCapIndicator-ESPNow_comp_radio_usb +SenseCapIndicator-LoRa_comp_radio_usb_wifi|SenseCapIndicator-LoRa_companion_radio_full|SenseCapIndicator-LoRa_comp_radio_usb_wifi +FULL_COMPANION_SPECS + +while IFS='|' read -r source_env full_env; do + [ "$(get_esp32_full_companion_replacement "$source_env")" = "$full_env" ] \ + || fail "$source_env did not collapse into $full_env" + is_redundant_bulk_build_target "$source_env" \ + || fail "$source_env remained beside its MQTT-capable Full Companion" + [ "${PIO_ENV_MQTT_BY_NAME[$full_env]:-1}" = 0 ] \ + || fail "$full_env was incorrectly classified as an MQTT-only profile" +done <<'MQTT_FULL_COMPANION_SPECS' +Heltec_v3_companion_radio_wifi_mqtt|Heltec_v3_companion_radio_full +heltec_v4_companion_radio_wifi_mqtt_femon|heltec_v4_2_v4_3_companion_radio_full_femon +heltec_v4_3_companion_radio_wifi_mqtt_femoff|heltec_v4_2_v4_3_companion_radio_full_femon +MQTT_FULL_COMPANION_SPECS + +for mqtt_override in on off; do + MQTT_BRIDGE_OVERRIDE=$mqtt_override + RESOLVED_BUILD_TARGETS=(Heltec_v3_companion_radio_full) + normalize_resolved_targets_for_mqtt build-companion-firmwares >/dev/null + [ "${RESOLVED_BUILD_TARGETS[*]}" = Heltec_v3_companion_radio_full ] \ + || fail "MQTT=${mqtt_override} replaced or discarded canonical Full Companion" + PLATFORMIO_BUILD_FLAGS="-DKEEP_FULL_RECIPE=1" + apply_mqtt_bridge_override Heltec_v3_companion_radio_full + [ "$PLATFORMIO_BUILD_FLAGS" = "-DKEEP_FULL_RECIPE=1" ] \ + || fail "MQTT=${mqtt_override} mutated canonical Full Companion flags" +done + +MQTT_BRIDGE_OVERRIDE=on +RESOLVED_BUILD_TARGETS=(Station_G2_companion_radio_full) +if normalize_resolved_targets_for_mqtt build-companion-firmwares >/dev/null; then + fail "MQTT=on retained a Full recipe without direct MQTT capability" +fi +MQTT_BRIDGE_OVERRIDE="" + +while IFS= read -r env_name; do + if [ "${PIO_ENV_PLATFORM_BY_NAME[$env_name]:-}" = ESP32_PLATFORM ] \ + && ! is_companion_radio_full_target "$env_name"; then + fail "canonical ESP32 Companion inventory still contains $env_name" + fi +done < <(resolve_companion_firmwares) + # Synthetic inventory: one ESP32 target qualified for expanded Full and one # nRF52 target which must attempt complete LoRa OTA in its current partition. SUPPORTED_PIO_ENVS=( @@ -506,6 +676,18 @@ expectations=" ${BUILD_EXPECTATIONS[*]} " [[ "$expectations" != *"Full Companion terminal listening"* ]] \ || fail "ESP32 Full contract still depends on optional debug logging" +pio_env_option_contains() { + [ "$1" = Heltec_v3_companion_radio_full ] \ + && [ "$2" = build_flags ] \ + && [ "$3" = WITH_MQTT_BRIDGE ] +} +BUILD_EXPECTATIONS=() +declare_build_capability_contract \ + Heltec_v3_companion_radio_full ESP32_PLATFORM +expectations=" ${BUILD_EXPECTATIONS[*]} " +[[ "$expectations" == *"companion.direct_mqtt=mqtt.status"* ]] \ + || fail "MQTT-capable ESP32 Full contract omitted direct MQTT" + PIO_ENV_PLATFORM_BY_NAME[RAK_3401_companion_radio_full]=NRF52_PLATFORM BUILD_CAPABILITIES=() BUILD_REDUCTIONS=() diff --git a/test/test_firmware_picker.js b/test/test_firmware_picker.js index c55635fc..306bc3d8 100644 --- a/test/test_firmware_picker.js +++ b/test/test_firmware_picker.js @@ -142,7 +142,7 @@ assert.strictEqual(companionFull.role, "companion"); assert.strictEqual(companionFull.mode, "full"); assert.strictEqual(companionFull.logging, "usb-runtime"); assert.deepStrictEqual(companionFull.loggingModes, ["none", "usb"]); -assert.strictEqual(companionFull.dedicatedUsbLogging, true); +assert.strictEqual(companionFull.dedicatedUsbLogging, false); assert.strictEqual(companionFull.ota, "lora-source"); assert.strictEqual(companionFull.feature, "full"); assert.strictEqual(companionFull.variant, "default"); @@ -186,7 +186,7 @@ assert.deepStrictEqual(standardBesideFull[0].loggingModes, ["none", "usb"]); const v4Full = profile("heltec_v4_2_v4_3_companion_radio_full_femon"); assert.strictEqual(v4Full.logging, "usb-runtime"); assert.deepStrictEqual(v4Full.loggingModes, ["none", "usb"]); -assert.strictEqual(v4Full.dedicatedUsbLogging, true); +assert.strictEqual(v4Full.dedicatedUsbLogging, false); ["usb", "ble", "wifi"].forEach(function (mode) { assert(!catalog.profiles.some(function (item) { return item.hardware === "heltec_v4" && item.role === "companion" && @@ -207,7 +207,7 @@ assert.strictEqual(v4Full.dedicatedUsbLogging, true); const classified = picker.applyFullCompanionCapabilities(candidates); assert.strictEqual(classified[0].logging, "usb-runtime"); assert.deepStrictEqual(classified[0].loggingModes, ["none", "usb"]); - assert.strictEqual(classified[0].dedicatedUsbLogging, undefined); + assert.strictEqual(classified[0].dedicatedUsbLogging, false); assert.deepStrictEqual( picker.omitTransportsReplacedByFull(classified).map(function (item) { return item.target; diff --git a/tools/mota/test_mota.py b/tools/mota/test_mota.py index e6348416..a399d7ff 100644 --- a/tools/mota/test_mota.py +++ b/tools/mota/test_mota.py @@ -92,8 +92,9 @@ def test_full_esp32_profile_unifies_usb_logging_and_wifi_mqtt(): assert 'uses_merged_standard_usb_logging "$target"' in matrix assert 'run_full_esp32_profile "FULL unified pass"' in matrix assert 'run_full_esp32_profile "FULL logging fallback pass"' in matrix - assert "Single-TTY boards first switch CDC 0" in mqtt_gate - assert "input-capable terminal" in mqtt_gate + assert "ESP32 uses one TTY" in mqtt_gate + assert "input-capable logging terminal" in mqtt_gate + assert "plaintext cannot mix with framed traffic" in mqtt_gate def test_espnow_tx_power_matches_cli_callback_contract(): @@ -201,17 +202,24 @@ def test_canonical_bulk_matrix_omits_runtime_and_transport_aliases(): assert "resolve_all_firmwares" in logging_matrix assert "print_nrf52_usb_logging_source_targets" not in build - full = build.split("apply_companion_radio_full_profile()", 1)[1] + full = build.split("apply_companion_radio_full_profile() {", 1)[1] full = full.split("get_firmware_filename()", 1)[0] assert "-DOTA_SEEDER_ONLY=1" in full assert "-DMOTA_TARGET_ID=0" in full assert "-UOTA_FLASH_STORE" in full assert "-UOTA_SD_STORE" in full assert "-UWEBCONFIG_DISABLED" in full - assert "-DCFG_TUD_CDC=2" in full - assert "-DMESH_DUAL_CDC_LOGGING=1" in full assert "-DMESH_DEBUG=1" in full assert "-DMESH_PACKET_LOGGING=1" in full + nrf52_full = full.split( + 'if is_nrf52_companion_radio_full_target "$env_name"', 1 + )[1].split("return 0", 1)[0] + assert "-DCFG_TUD_CDC=2" in nrf52_full + assert "-DMESH_DUAL_CDC_LOGGING=1" 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 + assert "-DCOMPANION_FEATURE_DEDICATED_USB_LOGGING=1" not in esp32_full assert 'disable_debug_flags "$env_name"' in build assert 'apply_debug_overrides "$env_name"' in build @@ -228,13 +236,7 @@ def test_canonical_bulk_matrix_omits_runtime_and_transport_aliases(): assert 'is_companion_radio_full_target "$env_name"' in disable_debug assert 'usb_logging_undefs=""' in disable_debug - esp32_dual = build.split( - "is_esp32_dual_cdc_companion_radio_full_target()", 1 - )[1].split("requires_esp32_companion_full_ota_fallback()", 1)[0] - assert "heltec_v4_2_v4_3_companion_radio_full_femon" in esp32_dual - assert "rak_3112_companion_radio_full" not in esp32_dual - assert "heltec_rc32_companion_radio_full" not in esp32_dual - assert "heltec_rc32_without_display_companion_radio_full" not in esp32_dual + assert "is_esp32_dual_cdc_companion_radio_full_target" not in build for relative, section, next_section in ( ( @@ -261,13 +263,11 @@ def test_canonical_bulk_matrix_omits_runtime_and_transport_aliases(): encoding="utf-8" ) assert "Adafruit_USBD_CDC dedicated_usb_logging_port" in usb_logging - assert "new (dedicated_usb_logging_port_storage)" in usb_logging - assert "USBCDC(1)" in usb_logging - assert "dedicated_usb_logging_port->enableReboot(false)" in usb_logging - assert "return result == ESP_OK && value != 0" in usb_logging + assert "supported only by nRF52 Full Companion" in usb_logging + assert "MESH_ESP32_DUAL_CDC_LOGGING" not in usb_logging + assert "USBCDC(1)" not in usb_logging assert "TinyUSBDevice.detach()" in usb_logging assert "return dedicated_usb_logging_port" in usb_logging - assert "return *dedicated_usb_logging_port" in usb_logging assert "return null_usb_logging_stream" in usb_logging companion = (root / "examples/companion_radio/MyMesh.cpp").read_text( @@ -276,8 +276,15 @@ def test_canonical_bulk_matrix_omits_runtime_and_transport_aliases(): companion_features = ( root / "examples/companion_radio/CompanionFeatures.h" ).read_text(encoding="utf-8") + companion_main = (root / "examples/companion_radio/main.cpp").read_text( + encoding="utf-8" + ) assert "COMPANION_FEATURE_DEDICATED_USB_LOGGING" in companion_features assert "defined(COMPANION_RADIO_FULL)" in companion + assert "if (!mesh::hasDedicatedUsbLoggingPort())" in companion_main + assert "if (mesh::isUsbLoggingEnabled())" in companion_main + assert "enterUsbLoggingTerminalMode();" in companion_main + assert "usb_logging_terminal_mode" in companion_main assert "_prefs.usb_logging_enabled = 0" in companion assert 'strcmp(value, "on reboot") == 0' in companion assert 'strcmp(value, "off reboot") == 0' in companion @@ -299,6 +306,49 @@ def test_canonical_bulk_matrix_omits_runtime_and_transport_aliases(): assert "is_esp32_dual_cdc_companion_radio_full_target" not in replacement +def test_single_tty_logging_off_requires_a_subsequent_mode_switch(): + root = Path(__file__).resolve().parents[2] + companion_main = ( + root / "examples/companion_radio/main.cpp" + ).read_text(encoding="utf-8") + service = companion_main.split( + "static void serviceUsbTerminal() {", 1 + )[1].split( + "static void expireUsbBinaryStartupProbeBeforeDispatch()", 1 + )[0] + + # A logging preference changed through BLE/WiFi must release logging's + # exclusive ownership without silently changing the protocol of an open + # USB ASCII session. + external_disable = service.split( + "else if (usb_logging_terminal_mode && the_mesh.isTerminalMode()) {", + 1, + )[1].split("\n }\n }\n#endif", 1)[0] + assert "usb_logging_terminal_mode = false;" in external_disable + assert "clearUsbTerminalLine();" in external_disable + assert "usb_terminal_discard_line = false;" in external_disable + assert "leaveUsbTerminalMode" not in external_disable + + # The same rule applies when `set usb.logging off` is entered on the USB + # logging terminal itself: keep the fresh-flash ASCII mode after replying. + command_disable = service.split( + "the_mesh.handleTerminalCommand(usb_terminal_line);", 1 + )[1].split('Serial.print("> ");', 1)[0] + assert "!mesh::isUsbLoggingEnabled()" in command_disable + assert "usb_logging_terminal_mode = false;" in command_disable + assert "leaveUsbTerminalMode" not in command_disable + + # Binary Companion remains reachable, but only through the ordinary mode + # switch that follows the logging-off command (explicit token or framed + # startup probe), never as a side effect of changing the preference. + terminal_stop = service.split( + "if (strcmp(usb_terminal_line, USB_TERMINAL_STOP_TOKEN) == 0) {", 1 + )[1].split("}", 1)[0] + assert "leaveUsbTerminalMode(true);" in terminal_stop + assert "if (!usb_logging_terminal_mode" in service + assert "usb_binary_startup_probe.shouldStart(" in service + + def test_measured_full_companion_promotions_are_exact_and_bounded(): root = Path(__file__).resolve().parents[2] build = (root / "build.sh").read_text(encoding="utf-8") @@ -395,25 +445,26 @@ def test_full_companion_wireless_startup_and_psram_contacts_are_resilient(): ) -def test_esp32_s3_full_profiles_inherit_dio_boot_mode(): +def test_esp32_s3_full_profiles_use_arduino2_and_dio_boot_mode(): root = Path(__file__).resolve().parents[2] project = (root / "platformio.ini").read_text(encoding="utf-8") + esp32_base = project.split("[esp32_base]", 1)[1].split("\n[", 1)[0] + assert "platformio/espressif32@6.11.0" in esp32_base shared = project.split("[esp32_s3_full]", 1)[1].split( "\n[", 1 )[0] assert "board_build.flash_mode = dio" in shared - - dual_cdc = project.split("[esp32_s3_dual_cdc_full]", 1)[1].split( - "\n[", 1 - )[0] - assert ( - "board_build.flash_mode = ${esp32_s3_full.board_build.flash_mode}" - in dual_cdc - ) - assert "esp32-core-3.3.11.tar.xz" in dual_cdc - assert "esp32-core-3.3.11-libs.tar.xz" in dual_cdc + assert "[esp32_s3_dual_cdc_full]" not in project build = (root / "build.sh").read_text(encoding="utf-8") + framework_selector = build.split( + "requires_esp32_arduino3_framework()", 1 + )[1].split("prepare_esp32_arduino3_framework()", 1)[0] + assert "heltec_rc32_" in framework_selector + assert ( + "is_esp32_dual_cdc_companion_radio_full_target" + not in framework_selector + ) framework_preflight = build.split( "prepare_esp32_arduino3_framework()", 1 )[1].split("requires_esp32_companion_full_ota_fallback()", 1)[0] @@ -461,8 +512,12 @@ def test_esp32_s3_full_profiles_inherit_dio_boot_mode(): "board_build.flash_mode = " "${esp32_s3_full.board_build.flash_mode}" ) in section, f"ESP32-S3 Full profile lacks shared DIO mode: {path}" + assert "esp32_s3_dual_cdc_full" not in section + if variant_dir != "heltec_rc32": + assert "55.03.311" not in section + assert "esp32-core-3.3.11" not in section - assert profile_count == 17 + assert profile_count == 18 def test_tbeam_1w_release_hardware_fixes_are_preserved(): @@ -542,18 +597,14 @@ def test_release_catalog_resolves_canonical_runtime_aliases(): provider = importlib.util.module_from_spec(spec) spec.loader.exec_module(provider) - assert provider.ESP32_DUAL_CDC_FULL_RE.match( - "heltec_v4_2_v4_3_companion_radio_full_femon" - ) - assert not provider.ESP32_DUAL_CDC_FULL_RE.match( - "RAK_3112_companion_radio_full" - ) - assert not provider.ESP32_DUAL_CDC_FULL_RE.match( - "heltec_rc32_companion_radio_full" - ) - assert not provider.ESP32_DUAL_CDC_FULL_RE.match( - "heltec_rc32_without_display_companion_radio_full" - ) + esp32_dual_cdc = getattr(provider, "ESP32_DUAL_CDC_FULL_RE", None) + for identity in ( + "heltec_v4_2_v4_3_companion_radio_full_femon", + "RAK_3112_companion_radio_full", + "heltec_rc32_companion_radio_full", + "heltec_rc32_without_display_companion_radio_full", + ): + assert esp32_dual_cdc is None or not esp32_dual_cdc.match(identity) release_files = { "RAK_4631_companion_radio_full": [Path("rak-full.zip")], @@ -626,21 +677,27 @@ def test_release_catalog_resolves_canonical_runtime_aliases(): assert "interface 02" in dual_notes assert "Input received on interface 02 is ignored" in dual_notes - v4_notes = provider.normalize_esp32_dual_cdc_full_companion_metadata( + v4_notes = provider.normalize_esp32_full_companion_metadata( {"title": "Companion USB", "subTitle": "USB logging"}, "PROFILE - old profile\n\nLOGGING USE - old use\n\nSELECTION - USB.", ) - assert "interface 00" in v4_notes - assert "interface 02" in v4_notes - assert "cannot reboot the board" in v4_notes - assert "ordinary Wi-Fi" in v4_notes + assert "input-capable plaintext" in v4_notes + assert "set usb.logging off" in v4_notes + assert "remains in the normal ASCII terminal" in v4_notes + assert "normal terminal stop token" in v4_notes + assert "valid framed probe" in v4_notes + assert "do not share the single TTY" in v4_notes + assert "interface 02" not in v4_notes - single_tty_notes = provider.normalize_esp32_single_tty_full_companion_metadata( + single_tty_notes = provider.normalize_esp32_full_companion_metadata( {"title": "Companion USB", "subTitle": "USB logging"}, "PROFILE - old profile\n\nLOGGING USE - old use\n\nSELECTION - USB.", ) assert "input-capable plaintext" in single_tty_notes assert "set usb.logging off" in single_tty_notes + assert "remains in the normal ASCII terminal" in single_tty_notes + assert "normal terminal stop token" in single_tty_notes + assert "valid framed probe" in single_tty_notes assert "do not share the single TTY" in single_tty_notes legacy = { diff --git a/variants/heltec_rc32/platformio.ini b/variants/heltec_rc32/platformio.ini index a8081e5c..6a9e0e2a 100644 --- a/variants/heltec_rc32/platformio.ini +++ b/variants/heltec_rc32/platformio.ini @@ -207,8 +207,7 @@ build_flags = -D ADVERT_LAT=0.0 -D ADVERT_LON=0.0 -D ADMIN_PASSWORD='"password"' - -D ENV_PIN_SDA=21 - -D ENV_PIN_SCL=18 + ; Environmental sensors share the board Wire bus at GPIO21/18. build_src_filter = ${Heltec_RC32.build_src_filter} +<../examples/simple_sensor> lib_deps = @@ -350,8 +349,7 @@ build_flags = -D ADVERT_LAT=0.0 -D ADVERT_LON=0.0 -D ADMIN_PASSWORD='"password"' - -D ENV_PIN_SDA=21 - -D ENV_PIN_SCL=18 + ; Environmental sensors share the board Wire bus at GPIO21/18. build_src_filter = ${Heltec_RC32_with_display.build_src_filter} +<../examples/simple_sensor> lib_deps = diff --git a/variants/heltec_t096/platformio.ini b/variants/heltec_t096/platformio.ini index 5fefe805..646b1685 100644 --- a/variants/heltec_t096/platformio.ini +++ b/variants/heltec_t096/platformio.ini @@ -198,6 +198,9 @@ build_flags = -D MAX_GROUP_CHANNELS=40 -D BLE_PIN_CODE=123456 -D ENV_INCLUDE_GPS=1 ; enable the GPS page in UI + ; External Grove/Qwiic sensors are on the board's second I2C controller. + -D ENV_PIN_SDA=PIN_WIRE1_SDA + -D ENV_PIN_SCL=PIN_WIRE1_SCL ; -D BLE_DEBUG_LOGGING=1 -D OFFLINE_QUEUE_SIZE=256 ; -D MESH_PACKET_LOGGING=1 @@ -227,6 +230,9 @@ build_flags = -D MAX_CONTACTS=350 -D MAX_GROUP_CHANNELS=40 -D ENABLE_USB_INTERFACE + ; The synthesized Full Companion inherits this verified external sensor bus. + -D ENV_PIN_SDA=PIN_WIRE1_SDA + -D ENV_PIN_SCL=PIN_WIRE1_SCL ; -D BLE_PIN_CODE=123456 ; -D BLE_DEBUG_LOGGING=1 ; -D MESH_PACKET_LOGGING=1 diff --git a/variants/heltec_tracker_v2/platformio.ini b/variants/heltec_tracker_v2/platformio.ini index 99bd3b8e..05571119 100644 --- a/variants/heltec_tracker_v2/platformio.ini +++ b/variants/heltec_tracker_v2/platformio.ini @@ -416,12 +416,9 @@ lib_deps = [env:heltec_tracker_v2_companion_radio_full_femon] extends = env:heltec_tracker_v2_companion_radio_wifi_femon -platform = ${esp32_s3_dual_cdc_full.platform} board_build.flash_mode = ${esp32_s3_full.board_build.flash_mode} -build_unflags = ${esp32_s3_dual_cdc_full.build_unflags} build_flags = ${env:heltec_tracker_v2_companion_radio_wifi_femon.build_flags} - ${esp32_s3_dual_cdc_full.build_flags} [env:heltec_tracker_v2_companion_radio_wifi_femoff] extends = env:heltec_tracker_v2_companion_radio_wifi_femon diff --git a/variants/heltec_v3/platformio.ini b/variants/heltec_v3/platformio.ini index 1fcc0c0f..d0eb7768 100644 --- a/variants/heltec_v3/platformio.ini +++ b/variants/heltec_v3/platformio.ini @@ -274,6 +274,10 @@ build_flags = -D MAX_GROUP_CHANNELS=40 -D DISPLAY_CLASS=SSD1306Display -D ENABLE_USB_INTERFACE + ; Preserve the established external-sensor header convention. The onboard + ; OLED remains on the board Wire bus at GPIO17/18. + -D ENV_PIN_SDA=33 + -D ENV_PIN_SCL=34 ; NOTE: DO NOT ENABLE --> -D MESH_PACKET_LOGGING=1 ; NOTE: DO NOT ENABLE --> -D MESH_DEBUG=1 build_src_filter = ${Heltec_lora32_v3.build_src_filter} @@ -294,6 +298,8 @@ build_flags = -D MAX_GROUP_CHANNELS=40 -D DISPLAY_CLASS=SSD1306Display -D BLE_PIN_CODE=123456 ; dynamic, random PIN + -D ENV_PIN_SDA=33 + -D ENV_PIN_SCL=34 -D AUTO_SHUTDOWN_MILLIVOLTS=3400 -D BLE_DEBUG_LOGGING=1 -D OFFLINE_QUEUE_SIZE=256 @@ -321,6 +327,10 @@ build_flags = -D MAX_GROUP_CHANNELS=40 -D DISPLAY_CLASS=SSD1306Display -D WIFI_DEBUG_LOGGING=1 + ; Full Companion is synthesized from this profile and must retain the + ; external sensor bus rather than probing the OLED bus. + -D ENV_PIN_SDA=33 + -D ENV_PIN_SCL=34 -D WIFI_SSID='"myssid"' -D WIFI_PWD='"mypwd"' -D OFFLINE_QUEUE_SIZE=256 @@ -363,6 +373,13 @@ lib_deps = JChristensen/Timezone paulstoffregen/Time@1.6.1 +; One canonical Companion image carries USB, BLE, ordinary WiFi, direct MQTT, +; WebConfig, logging, and source-only LoRa OTA. The legacy transport targets +; remain available for explicit compatibility builds. +[env:Heltec_v3_companion_radio_full] +extends = env:Heltec_v3_companion_radio_wifi_mqtt +board_build.flash_mode = ${esp32_s3_full.board_build.flash_mode} + [env:Heltec_v3_sensor] extends = Heltec_lora32_v3 build_flags = diff --git a/variants/heltec_v4/platformio.ini b/variants/heltec_v4/platformio.ini index 278eb158..6eca1ff8 100644 --- a/variants/heltec_v4/platformio.ini +++ b/variants/heltec_v4/platformio.ini @@ -57,6 +57,9 @@ build_flags = -D HELTEC_LORA_V4_OLED -D PIN_BOARD_SDA=17 -D PIN_BOARD_SCL=18 + ; The OLED uses Wire above; the expansion/general sensor connector is 4/3. + -D ENV_PIN_SDA=4 + -D ENV_PIN_SCL=3 -D PIN_OLED_RESET=21 build_src_filter= ${Heltec_lora32_v4.build_src_filter} lib_deps = ${Heltec_lora32_v4.lib_deps} @@ -520,29 +523,6 @@ build_flags = ${env:heltec_v4_companion_radio_wifi_femon.build_flags} -D RADIO_FEM_RXGAIN=0 ; undefined (default on), 1=on, 0=off -; Full Companion needs two independent native USB CDC interfaces. The pinned -; Arduino-ESP32 2.0.x platform supports only one; the 3.x platform already used -; by this tree's C6 targets supplies CDC 0 + CDC 1 on ESP32-S3. -[env:heltec_v4_2_v4_3_companion_radio_full_femon] -extends = env:heltec_v4_companion_radio_wifi_femon -platform = ${esp32_s3_dual_cdc_full.platform} -; Marking the merged image QIO makes this V4 reset in the ROM before the -; software bootloader can start. Keep the Full image on the shared DIO policy. -board_build.flash_mode = ${esp32_s3_full.board_build.flash_mode} -build_unflags = ${esp32_s3_dual_cdc_full.build_unflags} -build_flags = - ${env:heltec_v4_companion_radio_wifi_femon.build_flags} - ${esp32_s3_dual_cdc_full.build_flags} - -[env:heltec_v4_3_companion_radio_full_femoff] -extends = env:heltec_v4_3_companion_radio_wifi_femoff -platform = ${esp32_s3_dual_cdc_full.platform} -board_build.flash_mode = ${esp32_s3_full.board_build.flash_mode} -build_unflags = ${esp32_s3_dual_cdc_full.build_unflags} -build_flags = - ${env:heltec_v4_3_companion_radio_wifi_femoff.build_flags} - ${esp32_s3_dual_cdc_full.build_flags} - [env:heltec_v4_companion_radio_wifi_mqtt_femon] extends = env:heltec_v4_companion_radio_wifi_femon extra_scripts = @@ -576,6 +556,24 @@ build_flags = ${env:heltec_v4_companion_radio_wifi_mqtt_femon.build_flags} -D RADIO_FEM_RXGAIN=0 ; undefined (default on), 1=on, 0=off +; Full Companion subsumes the former direct-MQTT Companion so this is the one +; canonical V4 OLED image. Arduino-ESP32 2.x exposes one USB TTY; enabling USB +; logging gives that TTY exclusively to the plaintext CLI/logging stream until +; logging is disabled again. +[env:heltec_v4_2_v4_3_companion_radio_full_femon] +extends = env:heltec_v4_companion_radio_wifi_mqtt_femon +; Marking the merged image QIO makes this V4 reset in the ROM before the +; software bootloader can start. Keep the Full image on the shared DIO policy. +board_build.flash_mode = ${esp32_s3_full.board_build.flash_mode} +build_flags = + ${env:heltec_v4_companion_radio_wifi_mqtt_femon.build_flags} + +[env:heltec_v4_3_companion_radio_full_femoff] +extends = env:heltec_v4_3_companion_radio_wifi_mqtt_femoff +board_build.flash_mode = ${esp32_s3_full.board_build.flash_mode} +build_flags = + ${env:heltec_v4_3_companion_radio_wifi_mqtt_femoff.build_flags} + [env:heltec_v4_sensor] extends = heltec_v4_oled build_flags = @@ -584,8 +582,6 @@ build_flags = -D ADVERT_LAT=0.0 -D ADVERT_LON=0.0 -D ADMIN_PASSWORD='"password"' - -D ENV_PIN_SDA=3 - -D ENV_PIN_SCL=4 -D DISPLAY_CLASS=SSD1306Display ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 @@ -733,8 +729,7 @@ build_flags = extends = env:heltec_v4_tft_companion_radio_ble_femon build_flags = ${env:heltec_v4_tft_companion_radio_ble_femon.build_flags} - -D ENV_PIN_SDA=4 - -D ENV_PIN_SCL=3 + ; The TFT profile already uses Wire at the expansion I2C pins, GPIO4/3. [env:heltec_v4_3_expansionkit_tft_companion_radio_ble_femoff] extends = env:heltec_v4_expansionkit_tft_companion_radio_ble_femon @@ -745,6 +740,15 @@ build_flags = [env:heltec_v4_expansionkit_tft_companion_radio_ble_ps] extends = env:heltec_v4_expansionkit_tft_companion_radio_ble_femon +; The expansion-kit layout has different I2C ownership from the base TFT. Its +; Full image retains that exact hardware recipe while using the common +; Arduino-ESP32 2.x single-TTY policy. +[env:heltec_v4_expansionkit_tft_companion_radio_full_femon] +extends = env:heltec_v4_expansionkit_tft_companion_radio_ble_femon +board_build.flash_mode = ${esp32_s3_full.board_build.flash_mode} +build_flags = + ${env:heltec_v4_expansionkit_tft_companion_radio_ble_femon.build_flags} + [env:heltec_v4_tft_companion_radio_wifi_femon] extends = heltec_v4_tft build_flags = @@ -777,21 +781,15 @@ build_flags = [env:heltec_v4_tft_companion_radio_full_femon] extends = env:heltec_v4_tft_companion_radio_wifi_femon -platform = ${esp32_s3_dual_cdc_full.platform} board_build.flash_mode = ${esp32_s3_full.board_build.flash_mode} -build_unflags = ${esp32_s3_dual_cdc_full.build_unflags} build_flags = ${env:heltec_v4_tft_companion_radio_wifi_femon.build_flags} - ${esp32_s3_dual_cdc_full.build_flags} [env:heltec_v4_3_tft_companion_radio_full_femoff] extends = env:heltec_v4_3_tft_companion_radio_wifi_femoff -platform = ${esp32_s3_dual_cdc_full.platform} board_build.flash_mode = ${esp32_s3_full.board_build.flash_mode} -build_unflags = ${esp32_s3_dual_cdc_full.build_unflags} build_flags = ${env:heltec_v4_3_tft_companion_radio_wifi_femoff.build_flags} - ${esp32_s3_dual_cdc_full.build_flags} [env:heltec_v4_tft_sensor] extends = heltec_v4_tft @@ -801,8 +799,7 @@ build_flags = -D ADVERT_LAT=0.0 -D ADVERT_LON=0.0 -D ADMIN_PASSWORD='"password"' - -D ENV_PIN_SDA=3 - -D ENV_PIN_SCL=4 + ; Environmental sensors use the TFT profile's Wire bus at GPIO4/3. -D DISPLAY_CLASS=ST7789LCDDisplay ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 diff --git a/variants/heltec_v4/target.h b/variants/heltec_v4/target.h index 847f4fad..bb3a55e1 100644 --- a/variants/heltec_v4/target.h +++ b/variants/heltec_v4/target.h @@ -1,5 +1,9 @@ #pragma once +// GPIO40 and GPIO41 are hard-wired to GNSS WAKE and PPS respectively. +// Keep them unavailable even when no GNSS data is detected at startup. +#define USER_GPIO_RESERVED_PINS 40, 41 + #define RADIOLIB_STATIC_ONLY 1 #include #include @@ -34,4 +38,3 @@ extern EnvironmentSensorManager sensors; bool radio_init(); mesh::LocalIdentity radio_new_identity(); - diff --git a/variants/heltec_v4_r8/platformio.ini b/variants/heltec_v4_r8/platformio.ini index 661b13b1..d36ef3cb 100644 --- a/variants/heltec_v4_r8/platformio.ini +++ b/variants/heltec_v4_r8/platformio.ini @@ -196,12 +196,9 @@ lib_deps = [env:heltec_v4_r8_companion_radio_full] extends = env:heltec_v4_r8_companion_radio_wifi -platform = ${esp32_s3_dual_cdc_full.platform} board_build.flash_mode = ${esp32_s3_full.board_build.flash_mode} -build_unflags = ${esp32_s3_dual_cdc_full.build_unflags} build_flags = ${env:heltec_v4_r8_companion_radio_wifi.build_flags} - ${esp32_s3_dual_cdc_full.build_flags} [env:heltec_v4_r8_sensor] extends = heltec_v4_r8_oled @@ -330,12 +327,9 @@ lib_deps = [env:heltec_v4_r8_tft_companion_radio_full] extends = env:heltec_v4_r8_tft_companion_radio_wifi -platform = ${esp32_s3_dual_cdc_full.platform} board_build.flash_mode = ${esp32_s3_full.board_build.flash_mode} -build_unflags = ${esp32_s3_dual_cdc_full.build_unflags} build_flags = ${env:heltec_v4_r8_tft_companion_radio_wifi.build_flags} - ${esp32_s3_dual_cdc_full.build_flags} [env:heltec_v4_r8_tft_sensor] extends = heltec_v4_r8_tft diff --git a/variants/lilygo_tbeam_1w/platformio.ini b/variants/lilygo_tbeam_1w/platformio.ini index 2d78a9ba..7eeda27c 100644 --- a/variants/lilygo_tbeam_1w/platformio.ini +++ b/variants/lilygo_tbeam_1w/platformio.ini @@ -187,12 +187,9 @@ lib_deps = [env:LilyGo_TBeam_1W_companion_radio_full] extends = env:LilyGo_TBeam_1W_companion_radio_wifi -platform = ${esp32_s3_dual_cdc_full.platform} board_build.flash_mode = ${esp32_s3_full.board_build.flash_mode} -build_unflags = ${esp32_s3_dual_cdc_full.build_unflags} build_flags = ${env:LilyGo_TBeam_1W_companion_radio_wifi.build_flags} - ${esp32_s3_dual_cdc_full.build_flags} ; === LILYGO T-Beam 1W Repeater with ESPNow Bridge === [env:LilyGo_TBeam_1W_repeater_bridge_espnow] diff --git a/variants/meshnology_w12/platformio.ini b/variants/meshnology_w12/platformio.ini index 0bfc3f1b..5133a953 100644 --- a/variants/meshnology_w12/platformio.ini +++ b/variants/meshnology_w12/platformio.ini @@ -193,12 +193,9 @@ lib_deps = [env:meshnology_w12_companion_radio_full] extends = env:meshnology_w12_companion_radio_wifi -platform = ${esp32_s3_dual_cdc_full.platform} board_build.flash_mode = ${esp32_s3_full.board_build.flash_mode} -build_unflags = ${esp32_s3_dual_cdc_full.build_unflags} build_flags = ${env:meshnology_w12_companion_radio_wifi.build_flags} - ${esp32_s3_dual_cdc_full.build_flags} [env:meshnology_w12_sensor] extends = meshnology_w12 @@ -208,8 +205,7 @@ build_flags = -D ADVERT_LAT=0.0 -D ADVERT_LON=0.0 -D ADMIN_PASSWORD='"password"' - -D ENV_PIN_SDA=3 - -D ENV_PIN_SCL=4 + ; Sensors and the OLED share Wire at GPIO17/18. GPIO3/4 control the RF PAs. -D DISPLAY_CLASS=SSD1306Display ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 diff --git a/variants/nibble_screen_connect/platformio.ini b/variants/nibble_screen_connect/platformio.ini index d71784a1..8c09b82c 100644 --- a/variants/nibble_screen_connect/platformio.ini +++ b/variants/nibble_screen_connect/platformio.ini @@ -163,12 +163,9 @@ lib_deps = [env:nibble_screen_connect_companion_radio_full_] extends = env:nibble_screen_connect_companion_radio_wifi_ -platform = ${esp32_s3_dual_cdc_full.platform} board_build.flash_mode = ${esp32_s3_full.board_build.flash_mode} -build_unflags = ${esp32_s3_dual_cdc_full.build_unflags} build_flags = ${env:nibble_screen_connect_companion_radio_wifi_.build_flags} - ${esp32_s3_dual_cdc_full.build_flags} [env:nibble_screen_connect_kiss_modem_] diff --git a/variants/nibble_zero_connect/platformio.ini b/variants/nibble_zero_connect/platformio.ini index 1fa90aa6..8f3f3e3b 100644 --- a/variants/nibble_zero_connect/platformio.ini +++ b/variants/nibble_zero_connect/platformio.ini @@ -159,9 +159,6 @@ lib_deps = [env:nibble_zero_connect_companion_radio_full_] extends = env:nibble_zero_connect_companion_radio_wifi_ -platform = ${esp32_s3_dual_cdc_full.platform} board_build.flash_mode = ${esp32_s3_full.board_build.flash_mode} -build_unflags = ${esp32_s3_dual_cdc_full.build_unflags} build_flags = ${env:nibble_zero_connect_companion_radio_wifi_.build_flags} - ${esp32_s3_dual_cdc_full.build_flags} diff --git a/variants/rak3112/platformio.ini b/variants/rak3112/platformio.ini index 0fd70ffb..d5a5543a 100644 --- a/variants/rak3112/platformio.ini +++ b/variants/rak3112/platformio.ini @@ -137,8 +137,10 @@ build_flags = # -D MQTT_PORT=1883 # -D MQTT_USERNAME='"your-username"' # -D MQTT_PASSWORD='"your-password"' - -D ENV_PIN_SDA=33 - -D ENV_PIN_SCL=34 + ; Secondary I2C exposed by the RAK3112 module; GPIO33/34 can belong to + ; octal PSRAM and are not safe sensor pins. + -D ENV_PIN_SDA=17 + -D ENV_PIN_SCL=18 build_src_filter = ${rak3112.build_src_filter} + + @@ -199,8 +201,8 @@ build_flags = -D ESP32_CPU_FREQ=160 -D MQTT_WIFI_TX_POWER=WIFI_POWER_11dBm -D WITH_SNMP=1 - -D ENV_PIN_SDA=33 - -D ENV_PIN_SCL=34 + -D ENV_PIN_SDA=17 + -D ENV_PIN_SCL=18 build_src_filter = ${rak3112.build_src_filter} + + @@ -293,6 +295,11 @@ lib_deps = [env:RAK_3112_companion_radio_full] extends = env:RAK_3112_companion_radio_wifi board_build.flash_mode = ${esp32_s3_full.board_build.flash_mode} +build_flags = + ${env:RAK_3112_companion_radio_wifi.build_flags} + ; Keep the primary Wire bus at 9/40 and put external sensors on Wire1. + -D ENV_PIN_SDA=17 + -D ENV_PIN_SCL=18 [env:RAK_3112_sensor] extends = rak3112 @@ -302,8 +309,8 @@ build_flags = -D ADVERT_LAT=0.0 -D ADVERT_LON=0.0 -D ADMIN_PASSWORD='"password"' - -D ENV_PIN_SDA=33 - -D ENV_PIN_SCL=34 + -D ENV_PIN_SDA=17 + -D ENV_PIN_SCL=18 ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 build_src_filter = ${rak3112.build_src_filter} diff --git a/variants/station_g2/StationG2Board.h b/variants/station_g2/StationG2Board.h index d1989ee0..c98daa13 100644 --- a/variants/station_g2/StationG2Board.h +++ b/variants/station_g2/StationG2Board.h @@ -24,6 +24,21 @@ public: return 0; } + bool isUserGpioAvailable(uint8_t pin) const override { + // Only permit GPIOs exposed on the Station G2 IO extension socket. + switch (pin) { + case 8: + case 9: + case 16: + case 17: + case 18: + case 39: + return ESP32Board::isUserGpioAvailable(pin); + default: + return false; + } + } + const char* getManufacturerName() const override { return "Station G2"; } diff --git a/variants/station_g2/platformio.ini b/variants/station_g2/platformio.ini index e97666d9..d1424853 100644 --- a/variants/station_g2/platformio.ini +++ b/variants/station_g2/platformio.ini @@ -10,9 +10,9 @@ build_unflags = ; Preserve the original 0x10000/0x150000 dual-OTA layout so app-only updates do ; not require replacing the partition table or erasing an installed G2. board_build.partitions = default.csv -; The Arduino-ESP32 3.x board manifest defaults to QIO, but the Station G2 -; bootloader watchdog-resets before starting the app in that mode. The deployed -; hardware and the working 2.x bootloader both require DIO. +; The board manifest defaults to QIO, but the Station G2 bootloader +; watchdog-resets before starting the app in that mode. Deployed hardware and +; the working bootloader require DIO. board_build.flash_mode = dio build_flags = ${esp32_base.build_flags} @@ -331,14 +331,11 @@ lib_deps = [env:Station_G2_companion_radio_full] extends = env:Station_G2_companion_radio_wifi -platform = ${esp32_s3_dual_cdc_full.platform} board_build.flash_mode = ${esp32_s3_full.board_build.flash_mode} build_unflags = ${Station_G2.build_unflags} - ${esp32_s3_dual_cdc_full.build_unflags} build_flags = ${env:Station_G2_companion_radio_wifi.build_flags} - ${esp32_s3_dual_cdc_full.build_flags} [env:Station_G2_kiss_modem] extends = Station_G2 diff --git a/variants/station_g3_esp32/platformio.ini b/variants/station_g3_esp32/platformio.ini index 2eae4442..9222d8be 100644 --- a/variants/station_g3_esp32/platformio.ini +++ b/variants/station_g3_esp32/platformio.ini @@ -158,12 +158,9 @@ lib_deps = [env:Station_G3_ESP32_companion_radio_full] extends = env:Station_G3_ESP32_companion_radio_wifi -platform = ${esp32_s3_dual_cdc_full.platform} board_build.flash_mode = ${esp32_s3_full.board_build.flash_mode} -build_unflags = ${esp32_s3_dual_cdc_full.build_unflags} build_flags = ${env:Station_G3_ESP32_companion_radio_wifi.build_flags} - ${esp32_s3_dual_cdc_full.build_flags} [env:Station_G3_ESP32_kiss_modem] extends = Station_G3_ESP32 diff --git a/variants/xiao_s3_wio/platformio.ini b/variants/xiao_s3_wio/platformio.ini index ed270f80..425d75f2 100644 --- a/variants/xiao_s3_wio/platformio.ini +++ b/variants/xiao_s3_wio/platformio.ini @@ -307,12 +307,9 @@ lib_deps = [env:Xiao_S3_WIO_companion_radio_full] extends = env:Xiao_S3_WIO_companion_radio_wifi -platform = ${esp32_s3_dual_cdc_full.platform} board_build.flash_mode = ${esp32_s3_full.board_build.flash_mode} -build_unflags = ${esp32_s3_dual_cdc_full.build_unflags} build_flags = ${env:Xiao_S3_WIO_companion_radio_wifi.build_flags} - ${esp32_s3_dual_cdc_full.build_flags} -D SERIAL_TX=D6 -D SERIAL_RX=D7