Stabilize T-Beam full companion startup

This commit is contained in:
mikecarper
2026-08-16 11:00:55 -07:00
parent bcedfdd019
commit a514b2a6d5
8 changed files with 138 additions and 48 deletions
+6 -5
View File
@@ -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
+15 -12
View File
@@ -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
+89 -20
View File
@@ -5,6 +5,9 @@
#ifdef ESP32_PLATFORM
#include "esp_bt.h"
#if defined(COMPANION_RADIO_FULL)
#include <esp_heap_caps.h>
#endif
#endif
#ifdef TBEAM_1W
#include <esp_random.h>
@@ -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
}
+10 -9
View File
@@ -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;
}
+8 -1
View File
@@ -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"
+1
View File
@@ -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>
+6
View File
@@ -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;
+3 -1
View File
@@ -13,6 +13,9 @@
#include <helpers/ui/MomentaryButton.h>
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();