Give native USB time to recover after scheduled light sleep

This commit is contained in:
mikecarper committed 2026-10-28 03:10:32 -07:00
1 parent 5cbff19463
commit be6d636c79
3 files changed
+70 -8

No files matched your search

+16 -2
View File
@@ -1432,11 +1432,23 @@ bool ESP32Board::emergencySleepMilliseconds(uint32_t millisecs) {
#endif
const TickType_t usb_ticks = usb_held ? pdMS_TO_TICKS(millisecs) : 0;
if (usb_held && usb_ticks == 0) return false;
TickType_t usb_service_ticks = 0;
#if MESH_ESP32_USB_CONSOLE_COOPERATIVE
if (!usb_held) {
// SOF/connection state is sampled by RTOS hooks. Reserve awake service
// time after a timer wake so a newly attached host can become visible
// before the next sleep attempt. Keep the whole wait within this budget.
const uint32_t service_ms = millisecs > 100UL ? 100UL : millisecs;
usb_service_ticks = pdMS_TO_TICKS(service_ms);
if (usb_service_ticks == 0) return false;
millisecs -= service_ms;
}
#endif
if (watched && esp_task_wdt_reset() != ESP_OK) return false;
if (usb_held) {
if (usb_held || millisecs == 0) {
// This also remains safe during asynchronous wireless teardown. Do not
// arm or clear wake sources while yielding with the USB peripheral live.
vTaskDelay(usb_ticks);
vTaskDelay(usb_held ? usb_ticks : usb_service_ticks);
return !watched || esp_task_wdt_reset() == ESP_OK;
}
if (esp_sleep_enable_timer_wakeup(uint64_t(millisecs) * 1000ULL) != ESP_OK)
@@ -1445,6 +1457,8 @@ bool ESP32Board::emergencySleepMilliseconds(uint32_t millisecs) {
// normal sleep path installs its own next timer; never leave ours armed.
const esp_err_t slept = esp_light_sleep_start();
const esp_err_t cleared = esp_sleep_disable_wakeup_source(ESP_SLEEP_WAKEUP_TIMER);
if (slept == ESP_OK && cleared == ESP_OK && usb_service_ticks != 0)
vTaskDelay(usb_service_ticks);
const bool watchdog_ok = !watched || esp_task_wdt_reset() == ESP_OK;
return slept == ESP_OK && cleared == ESP_OK && watchdog_ok;
}
+48 -4
View File
@@ -223,9 +223,7 @@ int main() {
class EmergencySleepBackendTests(unittest.TestCase):
def test_actual_platform_waits_and_cleanup(self):
compiler = shutil.which("g++") or shutil.which("clang++")
self.assertIsNotNone(compiler)
def platform_code(self):
sources = {name: (ROOT / path).read_text() for name, path in (
("core", "src/MeshCore.h"),
("nrf", "src/helpers/NRF52Board.cpp"),
@@ -242,17 +240,63 @@ class EmergencySleepBackendTests(unittest.TestCase):
("NRF_SLEEP", "nrf", "bool NRF52Board::emergencySleepMilliseconds("),
("ESP_SLEEP", "esp", "bool ESP32Board::emergencySleepMilliseconds(")):
code = code.replace("@" + marker + "@", extract_braced(sources[source], signature))
return code
def run_code(self, code, flags=()):
compiler = shutil.which("g++") or shutil.which("clang++")
self.assertIsNotNone(compiler)
with tempfile.TemporaryDirectory(prefix="emergency-sleep-backends-") as directory:
work = Path(directory)
cpp, binary = work / "test.cpp", work / "test"
cpp.write_text(code)
subprocess.run([compiler, "-std=c++17", "-Wall", "-Wextra", "-Werror",
"-DCONFIG_ESP_TASK_WDT_TIMEOUT_S=4", *SANITIZERS,
"-DCONFIG_ESP_TASK_WDT_TIMEOUT_S=4", *flags, *SANITIZERS,
str(cpp), "-o", str(binary)], check=True,
capture_output=True, text=True, timeout=60)
subprocess.run([str(binary)], check=True, capture_output=True,
text=True, timeout=20)
def test_actual_platform_waits_and_cleanup(self):
self.run_code(self.platform_code())
def test_native_usb_timer_wake_reserves_enumeration_service_without_extending_budget(self):
code = self.platform_code().split("int main() {", 1)[0] + r'''
int main() {
ESP32Board esp;
reset(); assert(esp.emergencySleepMilliseconds(10000));
assert(last_armed_us == 9900000ULL && delayed_ticks == 100);
assert((events == std::vector<std::string>{"timer", "sleep", "clear_timer", "delay"}));
assert(timer_us == 0 && other_wake_sources == 0x71);
reset(); task_watchdog_status = ESP_OK;
assert(esp.emergencySleepMilliseconds(10000));
assert(last_armed_us == 1900000ULL && delayed_ticks == 100 && task_watchdog_feeds == 2);
assert((events == std::vector<std::string>{"task_feed", "timer", "sleep", "clear_timer", "delay", "task_feed"}));
reset(); assert(esp.emergencySleepMilliseconds(125));
assert(last_armed_us == 25000 && delayed_ticks == 100);
reset(); assert(esp.emergencySleepMilliseconds(30));
assert(delayed_ticks == 30 && events == std::vector<std::string>{"delay"});
assert(!timer_calls && !sleep_calls && !clear_calls);
reset(); tick_ms = 10;
assert(esp.emergencySleepMilliseconds(129));
assert(last_armed_us == 29000 && delayed_ticks == 10); // Timer + floored RTOS wait <=129ms.
reset(); tick_ms = 1000;
assert(!esp.emergencySleepMilliseconds(10000) && events.empty());
for (int* failed : {&sleep_error, &clear_error}) {
reset(); *failed = 9;
assert(!esp.emergencySleepMilliseconds(10000));
assert(!delayed_ticks && clear_calls == 1);
assert((events == std::vector<std::string>{"timer", "sleep", "clear_timer"}));
}
reset(); timer_error = 9;
assert(!esp.emergencySleepMilliseconds(10000));
assert(!delayed_ticks && !sleep_calls && !clear_calls);
reset(); usb_held = true; wireless_ready = false;
assert(esp.emergencySleepMilliseconds(10000));
assert(delayed_ticks == 100 && !sleep_calls); // Existing host still bypasses physical USB sleep.
}
'''
self.run_code(code, ("-DMESH_ESP32_USB_CONSOLE_COOPERATIVE=1",))
def test_disabled_feature_has_no_new_base_virtuals(self):
compiler = shutil.which("g++") or shutil.which("clang++")
self.assertIsNotNone(compiler)
+6 -2
View File
@@ -182,8 +182,12 @@ int main(){
def test_actual_infrastructure_loop_services_saved_choice_and_quiet_deadline(self):
for role in ("simple_repeater","simple_room_server","simple_sensor"):
source=(ROOT/"examples"/role/"main.cpp").read_text()
start=source.index(" board.servicePowerTestControls();",source.index("void loop()"))
end=source.index("#endif",start)
loop_start=source.index("void loop()")
clock_service=source.index("mesh::esp32::serviceCpuFrequencyPolicy(",loop_start)
# Emergency maintenance also services UART restoration before its
# early return. Keep this fixture anchored to the clock policy.
start=source.rindex(" board.servicePowerTestControls();",loop_start,clock_service)
end=source.index("#endif",clock_service)
service=source[start:end]
tests="void role_service(){\n"+service+"}\n"+r'''
int main(){