From a4d991cd596b58f46eb5911f88f65e34f6af8e2d Mon Sep 17 00:00:00 2001 From: mikecarper Date: Tue, 6 Oct 2026 11:59:32 -0700 Subject: [PATCH] Record ESP32 power and AP regression qualification --- .../esp32_full_power_ap_2026-10-06.md | 124 +++++ .../esp32_full_power_ap_2026-10-06.safe.json | 473 ++++++++++++++++++ 2 files changed, 597 insertions(+) create mode 100644 docs/test-results/esp32_full_power_ap_2026-10-06.md create mode 100644 docs/test-results/esp32_full_power_ap_2026-10-06.safe.json diff --git a/docs/test-results/esp32_full_power_ap_2026-10-06.md b/docs/test-results/esp32_full_power_ap_2026-10-06.md new file mode 100644 index 000000000..3a37cd5d4 --- /dev/null +++ b/docs/test-results/esp32_full_power_ap_2026-10-06.md @@ -0,0 +1,124 @@ +# ESP32 Full idle power and compact AP validation — 2026-10-06 + +Tested firmware source: `219e936ef82d16539111f03bc7982dd5654412df`, after pulling +upstream through `5a7c07a6c`. Version: +`v1.17.1.9-halo-keymind-cascade-dev-powerap3-219e936e`. +PlatformIO commands ran one at a time in a native WSL checkout. +A later upstream test-only commit `f8e04f090` was merged without changing the +tested firmware sources; its 14 USB serial hygiene tests also passed. + +## Corrections + +Fresh expanded ESP32 Full profiles default USB packet logging off. Existing +saved logging and power-saving preferences remain authoritative. Repeater and +Room Server automatic setup with no saved SSID suspends unused network bridges +and their automatic retries. When the setup window ends, idle cleanup releases +the actual WiFi driver before sleep becomes eligible. Failed shutdown remains a +sleep blocker, and active/stopping WebConfig, MQTT, ESP-NOW, and OTA retain their +ownership. OTA resumes only bridges that were actually running beforehand. + +Native USB Companion idle handling now checks the actual WiFi and Bluetooth +drivers, GPS UART, pending work, logging, host presence, and host-loss grace. +An enabled Bluetooth controller still blocks manual light sleep. This does not +change the deliberate raw-UART console or primary ESP-NOW sleep constraints. + +Compact browser OTA and Companion setup use the shared scan-before-AP startup. +They require an AP-start event and correct live SDK configuration, protocol, +channel, and IP before reporting success. Scanner cancellation and stale Arduino +versus SDK state have explicit regressions. Browser OTA preserves inherited +LAN/ESP-NOW ownership, and repeated OTA start preserves joined clients on a +healthy AP. WebConfig setup disconnects STA to isolate its open setup API while +preserving an explicitly running ESP-NOW owner. + +Physical testing caught an additional regression in the first candidate: manual +WiFi setup suspended explicitly enabled ESP-NOW. The final implementation limits +that suspension to automatic boot setup. Manual starts and master radio off/on +now preserve the requested WiFi/ESP-NOW subset. The regression test executes the +real infrastructure backend and wireless controller for both roles; reverting +the manual wrapper to automatic suspension makes it fail. + +## Software checks + +All 98 focused host tests in 20 suites passed. They include extracted production +startup, idle cleanup, sleep guards, USB host grace, saved preferences, OTA bridge +restoration, AP fault injection, and real wireless controller subset restoration. +The new suites are included in the unit-test workflow. A separate shared AP +fixture compiles Arduino 2/3 and open/password setup variants. Negative controls +demonstrate rejection of stale live SDK AP state and revived idle bridges. + +All eight release builds passed linked capability, internal RAM, and app-slot +checks: + +| Target/profile | MCU/core | App bytes | App slot bytes | +| --- | --- | ---: | ---: | +| Heltec V4 Repeater Full | ESP32-S3 / Arduino 2.0.17 | 2,100,584 | 6,553,600 | +| Heltec V4 compact LoRa OTA | ESP32-S3 / Arduino 2.0.17 | 1,283,304 | 1,310,720 portable ceiling | +| Heltec V4 Room Server Full | ESP32-S3 / Arduino 2.0.17 | 2,035,864 | 6,553,600 | +| Heltec V4.3 Companion Full FEM off | ESP32-S3 / Arduino 2.0.17 | 2,362,280 | 6,553,600 | +| Heltec V4 Sensor Full | ESP32-S3 / Arduino 2.0.17 | 1,493,976 | 6,553,600 | +| Heltec V2 Repeater Full | classic ESP32 / Arduino 2.0.17 | 1,675,432 | 3,342,336 | +| Xiao C3 Repeater Full | ESP32-C3 / Arduino 2.0.17 | 1,826,728 | 2,031,616 | +| Heltec RC32 without display Repeater Full | ESP32-S3 / Arduino 3.3.11 | 2,277,304 | 6,553,600 | + +Full V4 SHA-256: +`ceb901afaba5f8012c49094122e30612dbf08518ebeffa51d8f92375cb52bc02`; +running body hash `8D26967FCC1A4984`; target ID `E792A051`. +Compact V4 SHA-256: +`7b00131b795b236ce42cf62a824fb5e30a3f934034a819bf5d7a5d71a55983c9`; +running body hash `EF44A3B4FE567589`; target ID `4D015420`. + +## Physical checks + +Mercerwood's Heltec V4.3 OLED was identified through its exact USB serial, board, +Repeater role, hardware family, running image hash/size, target ID, and expanded +two-slot partition layout. Only application slots were updated; partition +table, bootloader, identity, and ACL were preserved. + +| Boundary | Result | +| --- | --- | +| Automatic boot with no SSID, temporarily saved WebUI/ESP-NOW enabled | PASS: fresh boot, working discovered/joined setup AP and cached scan results; unused ESP-NOW stayed suspended. Temporary saved flags were restored before manual cases. | +| Explicit ESP-NOW off/on, then manual WiFi on | PASS: ESP-NOW remained on through WiFi startup, AP connection, and portal stop. | +| Master 2.4 GHz off/on | PASS: individual on rejected while master off; both independently requested WiFi and ESP-NOW restored afterward. | +| Full AP after WiFi toggle, ESP-NOW toggle, master restore, and OTA stop | PASS: fresh beacon, association, HTTP 200, setup UI/API, cached scan results, and requested ESP-NOW ownership. | +| Full OTA AP after master off and repeated after stop | PASS: fresh beacon, association, HTTP 200, usable update page. | +| Compact explicit AP, restart after stop, and implicit `start ota` | PASS in all three cycles: fresh beacon, association, HTTP 200, lightweight raw uploader. | +| Full → compact → Full application uploads | PASS: each exact qualified image/target and application slot switch verified; final Full body `8D26967FCC1A4984`. | +| Restoration | PASS: original saved preferences, public identity, ACL, runtime radio flags, Pi WiFi, and wired route verified; temporary profiles removed, serial released, no cleanup errors. | + +The compact direct-build alias displays `env:?` because the optional target-name +catalog has no entry for its ID. The harness stopped on that display after +upload, then resumed only after exact qualified body hash, size, version, +hardware, role, target ID, and slots were independently proven. Unknown bodies, +wrong IDs, and wrong named environments still reject. That guard stop is retained +in the sanitized evidence; it was not counted as a firmware pass. + +The final node runs the new Full image with its original settings. It still has +saved power saving off and USB logging enabled. USB is attached and no current +meter is available, so actual sleep/current is unmeasured. The automatic setup +window expiration and sleep eligibility are covered by production-code host +tests; the physical check did not wait through the entire setup window. + +Sanitized build, host-test, and physical evidence: +[`esp32_full_power_ap_2026-10-06.safe.json`](esp32_full_power_ap_2026-10-06.safe.json). +Actual ESP-NOW peer packet exchange, the reported iPhone client, saved-network +LAN recovery, and other boards' physical current/AP behavior were not qualified +by this transaction. + +## Board scope and limits + +The fixes use shared ESP32 code: Repeater/Room lifecycle, CommonCLI Full defaults, +ESP32Board browser OTA/sleep, and Companion setup. Other ESP32 boards inherit the +applicable fixes when rebuilt through their matching release profile. No normal +ESP32 board sleep override or alternate production AP path was found. Standalone +partition migration/legacy seed utilities retain separate AP code. nRF52, +RP2040, and STM32 do not use these ESP32 changes. + +Build and host-test coverage does not establish each board's battery current. +Raw UART consoles, active BLE/controller, GPS UART, Ethernet/serial Companion +interfaces, native USB host grace, primary ESP-NOW, and intentionally enabled +logging can still inhibit manual sleep. Saved power-saving off also remains off +after updating. There is no claim of 11–14 mA consumption from this validation. + +Future AP acceptance must verify fresh beacon discovery, association, working +pages/API, scan results, owner preservation, and cleanup on physical hardware. +CLI success and mocked SDK state alone are insufficient. diff --git a/docs/test-results/esp32_full_power_ap_2026-10-06.safe.json b/docs/test-results/esp32_full_power_ap_2026-10-06.safe.json new file mode 100644 index 000000000..1dc19b22f --- /dev/null +++ b/docs/test-results/esp32_full_power_ap_2026-10-06.safe.json @@ -0,0 +1,473 @@ +{ + "source": "219e936ef82d16539111f03bc7982dd5654412df", + "version": "v1.17.1.9-halo-keymind-cascade-dev-powerap3-219e936e", + "builds": [ + { + "kind": "full", + "target": "heltec_v4_repeater", + "environment": "heltec_v4_repeater_observer_mqtt", + "profile": "full", + "mcu": "esp32s3", + "app_bytes": 2100584, + "slot_bytes": 6553600, + "app_sha256": "ceb901afaba5f8012c49094122e30612dbf08518ebeffa51d8f92375cb52bc02", + "capabilities_verified": true, + "memory_gate_passed": true, + "internal_available_bytes": 186696, + "internal_required_bytes": 165824 + }, + { + "kind": "minimal", + "target": "heltec_v4_repeater_lora_ota_no_external_sensors", + "environment": "heltec_v4_repeater", + "profile": "standard", + "mcu": "esp32s3", + "app_bytes": 1283304, + "slot_bytes": 6553600, + "app_sha256": "7b00131b795b236ce42cf62a824fb5e30a3f934034a819bf5d7a5d71a55983c9", + "capabilities_verified": true, + "memory_gate_passed": true, + "internal_available_bytes": 241848, + "internal_required_bytes": 90048 + }, + { + "kind": "room", + "target": "heltec_v4_room_server", + "environment": "heltec_v4_room_server_observer_mqtt", + "profile": "full", + "mcu": "esp32s3", + "app_bytes": 2035864, + "slot_bytes": 6553600, + "app_sha256": "2701882bd5f397a22122d7b9556e73e4e1291e782c491ba28059627dc6d98b9a", + "capabilities_verified": true, + "memory_gate_passed": true, + "internal_available_bytes": 202768, + "internal_required_bytes": 159424 + }, + { + "kind": "companion", + "target": "heltec_v4_3_companion_radio_full_femoff", + "environment": "heltec_v4_3_companion_radio_full_femoff", + "profile": "full", + "mcu": "esp32s3", + "app_bytes": 2362280, + "slot_bytes": 6553600, + "app_sha256": "1da05e5df95c8e3745f192a16d66017d2a8c85d27df974ba2dc2ca8122d476fe", + "capabilities_verified": true, + "memory_gate_passed": true, + "internal_available_bytes": 233888, + "internal_required_bytes": 177920 + }, + { + "kind": "sensor", + "target": "heltec_v4_sensor", + "environment": "heltec_v4_sensor", + "profile": "full", + "mcu": "esp32s3", + "app_bytes": 1493976, + "slot_bytes": 6553600, + "app_sha256": "c640639e6e99ae43ba4375a222c078be2e327a8473164fe89d691f22a5f9067c", + "capabilities_verified": true, + "memory_gate_passed": true, + "internal_available_bytes": 276504, + "internal_required_bytes": 128632 + }, + { + "kind": "classic", + "target": "Heltec_v2_repeater", + "environment": "Heltec_v2_repeater", + "profile": "full", + "mcu": "esp32", + "app_bytes": 1675432, + "slot_bytes": 3342336, + "app_sha256": "5ea927f6015aaca9fce2cf68339946118bdc953aab6fe0d3daf52a6fa37970e0", + "capabilities_verified": true, + "memory_gate_passed": true, + "internal_available_bytes": 192624, + "internal_required_bytes": 161744 + }, + { + "kind": "c3", + "target": "Xiao_C3_repeater", + "environment": "Xiao_C3_repeater", + "profile": "full", + "mcu": "esp32c3", + "app_bytes": 1826728, + "slot_bytes": 2031616, + "app_sha256": "20b9694ec8c5a11ad48ca9df8d6dfe67c4501774bfcc7d509af7f9b31c776027", + "capabilities_verified": true, + "memory_gate_passed": true, + "internal_available_bytes": 181856, + "internal_required_bytes": 139200 + }, + { + "kind": "rc32", + "target": "heltec_rc32_without_display_repeater", + "environment": "heltec_rc32_without_display_repeater_observer_mqtt", + "profile": "full", + "mcu": "esp32s3", + "app_bytes": 2277304, + "slot_bytes": 6553600, + "app_sha256": "963a8162848da50ae2a6e2265b8e00ca66ab4c33ca6de1aee151713e717c202a", + "capabilities_verified": true, + "memory_gate_passed": true, + "internal_available_bytes": 177552, + "internal_required_bytes": 159680 + } + ], + "host_checks": { + "source": "219e936ef82d16539111f03bc7982dd5654412df", + "passed": true, + "test_count": 98, + "suites": [ + { + "file": "test/test_esp32_full_sleep.py", + "passed": true, + "tests": 2 + }, + { + "file": "test/test_esp32_full_power_lifecycle.py", + "passed": true, + "tests": 7 + }, + { + "file": "test/test_esp32_usb_sleep.py", + "passed": true, + "tests": 7 + }, + { + "file": "test/test_usb_host_sleep_policy.py", + "passed": true, + "tests": 1 + }, + { + "file": "test/test_logging_sleep_contract.py", + "passed": true, + "tests": 3 + }, + { + "file": "test/test_wifi_ota_start.py", + "passed": true, + "tests": 10 + }, + { + "file": "test/test_wifi_ota_lifecycle.py", + "passed": true, + "tests": 15 + }, + { + "file": "test/test_webconfig_ap_start.py", + "passed": true, + "tests": 1 + }, + { + "file": "test/test_webconfig_station_ownership.py", + "passed": true, + "tests": 6 + }, + { + "file": "test/test_esp32_wifi_scan_fix.py", + "passed": true, + "tests": 5 + }, + { + "file": "test/test_esp32_wifi_scan_recipes.py", + "passed": true, + "tests": 4 + }, + { + "file": "test/test_wireless_control.py", + "passed": true, + "tests": 4 + }, + { + "file": "test/test_espnow_runtime.py", + "passed": true, + "tests": 1 + }, + { + "file": "test/test_espnow_radio_lifecycle.py", + "passed": true, + "tests": 5 + }, + { + "file": "test/test_rs232_mqtt_runtime.py", + "passed": true, + "tests": 2 + }, + { + "file": "test/test_companion_wifi_session.py", + "passed": true, + "tests": 5 + }, + { + "file": "test/test_companion_contact_sleep_contract.py", + "passed": true, + "tests": 12 + }, + { + "file": "test/test_usb_logging_watchdog_roles.py", + "passed": true, + "tests": 3 + }, + { + "file": "test/test_battery_charge_cli.py", + "passed": true, + "tests": 3 + }, + { + "file": "test/test_tbeam_charge_target.py", + "passed": true, + "tests": 2 + } + ] + }, + "hardware": { + "cases": [ + { + "name": "upload_verified_8D26967FCC1A4984", + "passed": true, + "image_hash_verified": true, + "application_slot_changed": true, + "transport": "multipart" + }, + { + "name": "full_automatic_unconfigured_boot_ap", + "passed": true, + "fresh_beacon": true, + "associated": true, + "last_seen_ms": 0, + "frequency_mhz": 2412, + "bssid_sha256": "1287bbb9c9aecd02ff94be170a225216154149c63384369ca9f9023f7a15623b", + "http_status": 200, + "setup_api_verified": true, + "cached_scan_state": "done", + "cached_scan_failed": false, + "cached_network_count": 3, + "fresh_boot": true, + "uptime_secs": 7, + "saved_webui_enabled": true, + "saved_espnow_enabled": true, + "automatic_espnow_suspended": true + }, + { + "name": "full_automatic_boot_preferences_restored_before_manual", + "passed": true, + "saved_flags_restored": true, + "baseline_runtime_restored": true + }, + { + "name": "full_webconfig_after_wifi_off_on_espnow_off", + "passed": true, + "fresh_beacon": true, + "associated": true, + "last_seen_ms": 0, + "frequency_mhz": 2412, + "bssid_sha256": "1287bbb9c9aecd02ff94be170a225216154149c63384369ca9f9023f7a15623b", + "http_status": 200, + "setup_api_verified": true, + "cached_scan_state": "done", + "cached_scan_failed": false, + "cached_network_count": 3 + }, + { + "name": "full_espnow_off_on", + "passed": true, + "runtime_on_verified": true + }, + { + "name": "full_wifi_on_preserves_operator_espnow", + "passed": true, + "runtime_on_verified": true + }, + { + "name": "full_webconfig_after_espnow_off_on", + "passed": true, + "fresh_beacon": true, + "associated": true, + "last_seen_ms": 0, + "frequency_mhz": 2412, + "bssid_sha256": "1287bbb9c9aecd02ff94be170a225216154149c63384369ca9f9023f7a15623b", + "http_status": 200, + "setup_api_verified": true, + "cached_scan_state": "done", + "cached_scan_failed": false, + "cached_network_count": 2, + "operator_espnow_preserved": true + }, + { + "name": "full_master_off_on", + "passed": true, + "individual_on_rejected": true, + "captured_runtime_restored": true, + "independent_requested_owners_restored": true + }, + { + "name": "full_webconfig_after_master_restore", + "passed": true, + "fresh_beacon": true, + "associated": true, + "last_seen_ms": 0, + "frequency_mhz": 2412, + "bssid_sha256": "1287bbb9c9aecd02ff94be170a225216154149c63384369ca9f9023f7a15623b", + "http_status": 200, + "setup_api_verified": true, + "cached_scan_state": "done", + "cached_scan_failed": false, + "cached_network_count": 2, + "operator_espnow_preserved": true + }, + { + "name": "full_ota_after_master_off", + "passed": true, + "fresh_beacon": true, + "associated": true, + "last_seen_ms": 0, + "frequency_mhz": 2412, + "bssid_sha256": "1287bbb9c9aecd02ff94be170a225216154149c63384369ca9f9023f7a15623b", + "http_status": 200, + "page_sha256": "2ada609891d6393b54bdcc559bb2f7d2abb564d5cd27593b266706cc8d878ab4", + "raw_uploader": false + }, + { + "name": "full_ota_repeat_after_stop", + "passed": true, + "fresh_beacon": true, + "associated": true, + "last_seen_ms": 0, + "frequency_mhz": 2412, + "bssid_sha256": "1287bbb9c9aecd02ff94be170a225216154149c63384369ca9f9023f7a15623b", + "http_status": 200, + "page_sha256": "2ada609891d6393b54bdcc559bb2f7d2abb564d5cd27593b266706cc8d878ab4", + "raw_uploader": false + }, + { + "name": "full_webconfig_after_ota_stop", + "passed": true, + "fresh_beacon": true, + "associated": true, + "last_seen_ms": 0, + "frequency_mhz": 2412, + "bssid_sha256": "1287bbb9c9aecd02ff94be170a225216154149c63384369ca9f9023f7a15623b", + "http_status": 200, + "setup_api_verified": true, + "cached_scan_state": "done", + "cached_scan_failed": false, + "cached_network_count": 2, + "operator_espnow_preserved": true + }, + { + "name": "upload_verified_EF44A3B4FE567589", + "passed": true, + "image_hash_verified": true, + "application_slot_changed": true, + "transport": "multipart", + "target_id": "4D015420", + "artifact_env": "heltec_v4_repeater_lora_ota_no_external_sensors", + "observed_env_display": "?" + }, + { + "name": "minimal_explicit_ap", + "passed": true, + "fresh_beacon": true, + "associated": true, + "last_seen_ms": 0, + "frequency_mhz": 2412, + "bssid_sha256": "1287bbb9c9aecd02ff94be170a225216154149c63384369ca9f9023f7a15623b", + "http_status": 200, + "page_sha256": "7379d4c0bcc34c9a277c7faac9d97735693ee2a2fc27408ca81054433150a4d2", + "raw_uploader": true + }, + { + "name": "minimal_repeat_ap", + "passed": true, + "fresh_beacon": true, + "associated": true, + "last_seen_ms": 0, + "frequency_mhz": 2412, + "bssid_sha256": "1287bbb9c9aecd02ff94be170a225216154149c63384369ca9f9023f7a15623b", + "http_status": 200, + "page_sha256": "7379d4c0bcc34c9a277c7faac9d97735693ee2a2fc27408ca81054433150a4d2", + "raw_uploader": true + }, + { + "name": "minimal_implicit_ap", + "passed": true, + "fresh_beacon": true, + "associated": true, + "last_seen_ms": 0, + "frequency_mhz": 2412, + "bssid_sha256": "1287bbb9c9aecd02ff94be170a225216154149c63384369ca9f9023f7a15623b", + "http_status": 200, + "page_sha256": "7379d4c0bcc34c9a277c7faac9d97735693ee2a2fc27408ca81054433150a4d2", + "raw_uploader": true + }, + { + "name": "upload_verified_8D26967FCC1A4984", + "passed": true, + "image_hash_verified": true, + "application_slot_changed": true, + "transport": "raw" + } + ], + "ok": true, + "current_measured": false, + "sleep_measured": false, + "usb_powered": true, + "flashed": true, + "source": "219e936ef82d16539111f03bc7982dd5654412df", + "cleanup_errors": [], + "baseline_image": { + "running": { + "body_bytes": 2101904, + "image_bytes": 2101960, + "body_hash16": "9822F1C138EA6D86" + }, + "version": "v1.17.1.9-halo-keymind-cascade-dev-powerap2-bceea7e1 (Build: 06-Oct-2026)", + "active_slot": 6619136, + "inactive_slot": 65536, + "target_id": "E792A051", + "artifact_env": "heltec_v4_repeater" + }, + "baseline_powersaving": "off", + "baseline_logging_output": "usb", + "baseline_saved_ssid_empty": true, + "sleep_limit": "USB-powered; baseline powersaving off and USB logging on; no current measurement", + "last_upload_attempt": { + "expected_hash": "8D26967FCC1A4984", + "raw_uploader": true + }, + "temporary_saved_preferences_restored": true, + "serial_released": true, + "pi_wifi_restored": true, + "wired_route_preserved": true, + "temporary_profiles_removed": true, + "guard_stop_provenance": { + "error": "RuntimeError: exact V4 image did not reconnect", + "cleanup_errors": [ + "restore Full/node: AssertionError: running target identity differs" + ], + "evidence_path": "/home/mikec/hwtest/v4-power-ap-219e936e-20261006/transaction-safe.json" + }, + "resumed_after_optional_target_name_guard_stop": true, + "minimal_master_control_supported": false, + "full_restored": true, + "preferences_restored": true, + "identity_restored": true, + "acl_restored": true, + "runtime_restored": true, + "final_image": { + "body_hash16": "8D26967FCC1A4984", + "body_bytes": 2100528, + "image_bytes": 2100584, + "version": "v1.17.1.9-halo-keymind-cascade-dev-powerap3-219e936e (Build: 06-Oct-2026)", + "board": "Heltec V4.3 OLED", + "role": "repeater", + "hardware_id": "heltec_v4", + "target_id": "E792A051", + "artifact_env": "heltec_v4_repeater", + "observed_env_display": "heltec_v4_repeater", + "application_slot": 65536 + } + } +}