diff --git a/docs/WiFi.md b/docs/WiFi.md index 369650a3..e9544554 100644 --- a/docs/WiFi.md +++ b/docs/WiFi.md @@ -181,11 +181,12 @@ service on ports 5001 and 5002. See the [full Companion guide](./companion_radio_full.md) for its build, terminal mode, and complete update-source workflow. -The LilyGo T-Beam 1W Full Companion maps a triple click of the physical button -labeled `17` to a persistent WiFi on/off toggle. Turning WiFi off closes the -WebConfig, Companion TCP, mOTA, and MQTT network services while USB, BLE, the -display, GPS, and LoRa remain available. The same triple click turns WiFi back -on even after reboot. +The LilyGo T-Beam 1W Full Companion maps one press of the physical `BOOT` button +(GPIO0) to a persistent WiFi on/off toggle. Turning WiFi off closes WebConfig, +Companion TCP, mOTA, and MQTT network services while USB, BLE, the display, GPS, +and LoRa remain available. Another BOOT press turns WiFi back on even after +reboot. WiFi/WebConfig starts first; BLE starts two seconds later so the ESP32-S3 +can reserve the setup server's internal heap before both radios run together. The companion loads runtime credentials saved in NVS. A non-placeholder compile-time `WIFI_SSID`/`WIFI_PWD` can be used as a first-boot fallback, but diff --git a/docs/companion_radio_full.md b/docs/companion_radio_full.md index fdf6b4b3..fc16d027 100644 --- a/docs/companion_radio_full.md +++ b/docs/companion_radio_full.md @@ -80,21 +80,24 @@ clock and keeps GPS awake. Full Companion transports remain available in both states; WiFi modem sleep stays enabled when BLE is present because coexistence requires it. The selected state is retained after reboot. -On the LilyGo T-Beam 1W Full Companion, triple-click the physical button -labeled `17` to turn the ESP32 WiFi radio and all WiFi services off or on. The -screen confirms `WiFi: OFF` or `WiFi: ON`, and the selected state is retained -after reboot. When WiFi is off, TCP ports 5000-5002, WebConfig, and MQTT are -stopped; USB, BLE, the display, GPS, and LoRa continue to operate. Triple-click -button 17 again to restore WiFi, including the saved station or setup-AP mode. +On the LilyGo T-Beam 1W Full Companion, press the physical `BOOT` button once +to turn the ESP32 WiFi radio and all WiFi services off or on. The screen confirms +`WiFi: OFF` or `WiFi: ON`, and the selected state is retained after reboot. When +WiFi is off, TCP ports 5000-5002, WebConfig, and MQTT are stopped; USB, BLE, the +display, GPS, and LoRa continue to operate. Press BOOT again to restore WiFi, +including the saved station or setup-AP mode. On boot, BLE starts two seconds +after WiFi/WebConfig so their peak startup allocations do not overlap. Artifacts are written to `out/` by default. -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. Flash -the generated `-merged.bin` when first installing this partition layout. -Boards with 8 MB or more retain dual application partitions. Heltec V2 and -TLora V2 use 100 contacts, 8 group channels, and a 16-frame offline queue in -this combined profile because of internal DRAM limits. +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 +Companion uses that same LilyGo factory-compatible boot layout on its 16 MB +flash because this source-only role does not install updates into a second app +slot. Flash the generated `-merged.bin` when first installing this partition +layout. Other boards with 8 MB or more retain dual application partitions. +Heltec V2 and TLora V2 use 100 contacts, 8 group channels, and a 16-frame offline +queue in this combined profile because of internal DRAM limits. The nRF52 target inherits the board's ordinary USB Companion installation format and adds BLE plus the serial mOTA source. It does not enable an SD cache diff --git a/examples/companion_radio/main.cpp b/examples/companion_radio/main.cpp index 517a2bd1..b217c9f5 100644 --- a/examples/companion_radio/main.cpp +++ b/examples/companion_radio/main.cpp @@ -5,6 +5,9 @@ #ifdef ESP32_PLATFORM #include "esp_bt.h" +#if defined(COMPANION_RADIO_FULL) +#include +#endif #endif #ifdef TBEAM_1W #include @@ -464,6 +467,20 @@ void halt() { static bool companion_wifi_disable_in_progress = false; static bool companion_wifi_services_stopped = false; + static void loadCompanionWiFiCredentials() { + companion_wifi_has_credentials = WiFiSetupPortal::loadStoredCredentials( + configured_wifi_ssid, sizeof(configured_wifi_ssid), + configured_wifi_password, sizeof(configured_wifi_password)); + if (!companion_wifi_has_credentials + && !WiFiSetupPortal::isPlaceholderSSID(WIFI_SSID)) { + strncpy(configured_wifi_ssid, WIFI_SSID, sizeof(configured_wifi_ssid) - 1); + configured_wifi_ssid[sizeof(configured_wifi_ssid) - 1] = '\0'; + strncpy(configured_wifi_password, WIFI_PWD, sizeof(configured_wifi_password) - 1); + configured_wifi_password[sizeof(configured_wifi_password) - 1] = '\0'; + companion_wifi_has_credentials = true; + } + } + bool isCompanionWiFiEnabled() { return companion_wifi_requested; } @@ -475,7 +492,7 @@ void halt() { if (!companion_wifi_requested && companion_wifi_active) { companion_wifi_disable_in_progress = true; } - WIFI_DEBUG_PRINTLN("GPIO 17 triple click requested WiFi %s", + WIFI_DEBUG_PRINTLN("BOOT/GPIO 0 click requested WiFi %s", companion_wifi_requested ? "on" : "off"); return companion_wifi_requested; } @@ -651,7 +668,7 @@ void halt() { #endif companion_wifi_active = true; companion_wifi_services_stopped = false; - WIFI_DEBUG_PRINTLN("WiFi enabled by GPIO 17 control"); + WIFI_DEBUG_PRINTLN("WiFi enabled by BOOT/GPIO 0 control"); } static void stopCompanionWiFiServices() { @@ -688,7 +705,7 @@ void halt() { board.setInhibitSleep(false); resetCompanionWiFiRecoveryState(); companion_wifi_active = false; - WIFI_DEBUG_PRINTLN("WiFi radio disabled by GPIO 17 control"); + WIFI_DEBUG_PRINTLN("WiFi radio disabled by BOOT/GPIO 0 control"); return true; } @@ -706,6 +723,47 @@ void halt() { } #endif +#if defined(BLE_PIN_CODE) + static bool companion_bluetooth_initialized = false; +#if defined(ESP32) && defined(COMPANION_RADIO_FULL) && defined(WIFI_SSID) + static uint32_t companion_bluetooth_start_at = 0; +#endif + + static void startCompanionBluetooth() { + if (companion_bluetooth_initialized) return; + + Serial.println("Companion: starting Bluetooth"); + bluetooth_interface.begin(BLE_NAME_PREFIX, the_mesh.getNodePrefs()->node_name, + the_mesh.getBLEPin()); + if (!interface_manager.addInterface(InterfaceType::Bluetooth, + &bluetooth_interface)) { + Serial.println("Companion: no interface slot available for Bluetooth"); + return; + } + companion_bluetooth_initialized = true; + if (interface_manager.isEnabled()) bluetooth_interface.enable(); + } + + static void serviceDeferredCompanionBluetooth() { + if (companion_bluetooth_initialized) return; +#if defined(ESP32) && defined(COMPANION_RADIO_FULL) && defined(WIFI_SSID) + if (companion_bluetooth_start_at == 0 + || (int32_t)(millis() - companion_bluetooth_start_at) < 0) return; +#endif + startCompanionBluetooth(); + } +#endif + +#if defined(ESP32_PLATFORM) && defined(COMPANION_RADIO_FULL) + static void logFullCompanionMemory(const char* stage) { + Serial.printf( + "Full Companion memory %s: heap=%u largest_internal=%u psram_free=%u/%u\r\n", + stage, (unsigned)ESP.getFreeHeap(), + (unsigned)heap_caps_get_largest_free_block(MALLOC_CAP_INTERNAL), + (unsigned)ESP.getFreePsram(), (unsigned)ESP.getPsramSize()); + } +#endif + void setup() { Serial.begin(115200); board.begin(); @@ -827,10 +885,13 @@ void setup() { #error "need to define filesystem" #endif -// add bluetooth interface -#if defined(BLE_PIN_CODE) - bluetooth_interface.begin(BLE_NAME_PREFIX, the_mesh.getNodePrefs()->node_name, the_mesh.getBLEPin()); - interface_manager.addInterface(InterfaceType::Bluetooth, &bluetooth_interface); +// Load WiFi state before bringing up either wireless stack. +#ifdef WIFI_SSID + loadCompanionWiFiCredentials(); +#endif + +#if defined(ESP32_PLATFORM) && defined(COMPANION_RADIO_FULL) + logFullCompanionMemory("before interfaces"); #endif // add wifi interface @@ -846,18 +907,6 @@ void setup() { } }); - companion_wifi_has_credentials = WiFiSetupPortal::loadStoredCredentials( - configured_wifi_ssid, sizeof(configured_wifi_ssid), - configured_wifi_password, sizeof(configured_wifi_password)); - if (!companion_wifi_has_credentials - && !WiFiSetupPortal::isPlaceholderSSID(WIFI_SSID)) { - strncpy(configured_wifi_ssid, WIFI_SSID, sizeof(configured_wifi_ssid) - 1); - configured_wifi_ssid[sizeof(configured_wifi_ssid) - 1] = '\0'; - strncpy(configured_wifi_password, WIFI_PWD, sizeof(configured_wifi_password) - 1); - configured_wifi_password[sizeof(configured_wifi_password) - 1] = '\0'; - companion_wifi_has_credentials = true; - } - interface_manager.addInterface(InterfaceType::WiFi, &wifi_interface); companion_wifi_requested = the_mesh.getNodePrefs()->wifi_enabled != 0; if (companion_wifi_requested) { @@ -866,10 +915,27 @@ void setup() { WiFi.setAutoReconnect(false); WiFi.mode(WIFI_OFF); board.setInhibitSleep(false); - WIFI_DEBUG_PRINTLN("WiFi remains off from the saved GPIO 17 setting"); + WIFI_DEBUG_PRINTLN("WiFi remains off from the saved BOOT/GPIO 0 setting"); } #endif +#if defined(ESP32_PLATFORM) && defined(COMPANION_RADIO_FULL) + logFullCompanionMemory("after WiFi start"); +#endif + +// The Full ESP32 profile starts WiFi/WebConfig first, gives its AP/server two +// seconds to settle and reserve heap, then starts BLE from loop(). Other BLE +// companions keep their existing immediate startup. +#if defined(BLE_PIN_CODE) + #if defined(ESP32) && defined(COMPANION_RADIO_FULL) && defined(WIFI_SSID) + companion_bluetooth_start_at = millis() + 2000UL; + if (companion_bluetooth_start_at == 0) companion_bluetooth_start_at = 1; + Serial.println("Companion: Bluetooth starts in 2 seconds"); + #else + startCompanionBluetooth(); + #endif +#endif + // add usb interface #if defined(ENABLE_USB_INTERFACE) #if defined(NRF52_PLATFORM) && defined(COMPANION_RADIO_FULL) && defined(OTA_FOLDER_SERIAL) @@ -1080,4 +1146,7 @@ void loop() { #endif } #endif +#if defined(BLE_PIN_CODE) + serviceDeferredCompanionBluetooth(); +#endif } diff --git a/examples/companion_radio/ui-new/UITask.cpp b/examples/companion_radio/ui-new/UITask.cpp index 61df4244..bab0ef8f 100644 --- a/examples/companion_radio/ui-new/UITask.cpp +++ b/examples/companion_radio/ui-new/UITask.cpp @@ -598,6 +598,9 @@ void UITask::begin(DisplayDriver* display, SensorManager* sensors, CompanionNode #if defined(PIN_USER_BTN) user_btn.begin(); #endif +#if defined(TBEAM_1W) && defined(WIFI_SSID) && defined(PIN_WIFI_BTN) + wifi_btn.begin(); +#endif #if defined(PIN_USER_BTN_ANA) analog_btn.begin(); #endif @@ -628,9 +631,11 @@ void UITask::begin(DisplayDriver* display, SensorManager* sensors, CompanionNode } void UITask::serviceWiFiToggleButton() { -#if defined(TBEAM_1W) && defined(WIFI_SSID) && defined(PIN_USER_BTN) - if (user_btn.check() == BUTTON_EVENT_TRIPLE_CLICK) { - handleTripleClick(KEY_SELECT); +#if defined(TBEAM_1W) && defined(WIFI_SSID) && defined(PIN_WIFI_BTN) + if (wifi_btn.check() == BUTTON_EVENT_CLICK) { + const bool enabled = toggleCompanionWiFi(); + showAlert(enabled ? "WiFi: ON" : "WiFi: OFF", 1200); + _next_refresh = 0; } #endif } @@ -812,6 +817,8 @@ bool UITask::isButtonPressed() const { } void UITask::loop() { + serviceWiFiToggleButton(); + if (_interfaceManager->takePairingRequest()) { showPairingPin(); } @@ -1013,13 +1020,7 @@ char UITask::handleDoubleClick(char c) { char UITask::handleTripleClick(char c) { MESH_DEBUG_PRINTLN("UITask: triple click triggered"); checkDisplayOn(c); -#if defined(TBEAM_1W) && defined(WIFI_SSID) - const bool enabled = toggleCompanionWiFi(); - showAlert(enabled ? "WiFi: ON" : "WiFi: OFF", 1200); - _next_refresh = 0; -#else toggleBuzzer(); -#endif c = 0; return c; } diff --git a/scripts/esp32_full_partition.py b/scripts/esp32_full_partition.py index ba1918ae..d891da87 100644 --- a/scripts/esp32_full_partition.py +++ b/scripts/esp32_full_partition.py @@ -18,8 +18,15 @@ if os.environ.get("MESHCORE_ESP32_FULL_BUILD") == "1": companion_radio_full = ( os.environ.get("MESHCORE_COMPANION_RADIO_FULL") == "1" ) + tbeam_1w = board.get("build.variant", "") == "lilygo_tbeam_1w" - if flash_size >= 16 * 1024 * 1024: + if companion_radio_full and tbeam_1w: + # The Full Companion is an mOTA source only and never installs into a + # second local app slot. Match LilyGo's factory T-Beam 1W boot layout so + # a merged recovery image changes only the application, not the proven + # boot/partition chain. The 3 MiB app slot has ample room for this role. + partitions = "huge_app.csv" + elif flash_size >= 16 * 1024 * 1024: partitions = "default_16MB.csv" elif flash_size >= 8 * 1024 * 1024: partitions = "default_8MB.csv" diff --git a/variants/lilygo_tbeam_1w/platformio.ini b/variants/lilygo_tbeam_1w/platformio.ini index 5f685d95..0665b4d5 100644 --- a/variants/lilygo_tbeam_1w/platformio.ini +++ b/variants/lilygo_tbeam_1w/platformio.ini @@ -52,6 +52,7 @@ build_flags = ; User interface -D PIN_USER_BTN=17 + -D PIN_WIFI_BTN=0 build_src_filter = ${esp32_base.build_src_filter} +<../variants/lilygo_tbeam_1w> diff --git a/variants/lilygo_tbeam_1w/target.cpp b/variants/lilygo_tbeam_1w/target.cpp index 3e9ddaae..a8de201b 100644 --- a/variants/lilygo_tbeam_1w/target.cpp +++ b/variants/lilygo_tbeam_1w/target.cpp @@ -8,6 +8,12 @@ TBeam1WBoard board; #ifdef DISPLAY_CLASS DISPLAY_CLASS display; MomentaryButton user_btn(PIN_USER_BTN, 1000, true); + #ifdef PIN_WIFI_BTN + // The BOOT switch is also a normal active-low GPIO after startup. Treat it + // as a dedicated single-click control; the board supplies a 10K pull-up, + // and enabling the ESP32 pull-up as well keeps the input deterministic. + MomentaryButton wifi_btn(PIN_WIFI_BTN, 0, true, true, false); + #endif #endif static SPIClass spi; diff --git a/variants/lilygo_tbeam_1w/target.h b/variants/lilygo_tbeam_1w/target.h index d6cd61b4..b197d098 100644 --- a/variants/lilygo_tbeam_1w/target.h +++ b/variants/lilygo_tbeam_1w/target.h @@ -13,6 +13,9 @@ #include extern DISPLAY_CLASS display; extern MomentaryButton user_btn; + #ifdef PIN_WIFI_BTN + extern MomentaryButton wifi_btn; + #endif #endif extern TBeam1WBoard board; @@ -22,4 +25,3 @@ extern EnvironmentSensorManager sensors; bool radio_init(); mesh::LocalIdentity radio_new_identity(); -