Wake Heltec V4 buttons from power-saving sleep

This commit is contained in:
mikecarper committed 2026-10-05 23:51:26 -07:00
1 parent c88885a315
commit ebf2f0823e
7 files changed
+718 -6

No files matched your search

+15 -1
View File
@@ -11,6 +11,9 @@ layout before installing an image.
- ESP32 native USB recipes now use hardware CDC where the chip supports it.
Classic ESP32 and explicit UART transports keep their UART path. nRF52,
RP2040 and STM32 USB backends are unchanged by this migration.
- Heltec V4 and V4 R8 user buttons wake CPU light sleep when power saving is
enabled. Button debounce and click deadlines remain awake so the display can
process the press. Explicit display-off mode still suppresses screen wake.
- ESP32 USB output is bounded so a stopped reader cannot indefinitely hold
the radio loop. The G2 preserves complete serial packet-log lines under
congestion. This supports unmodified Cisien meshcoretomqtt; congested
@@ -126,7 +129,18 @@ endpoint, reported the WebConfig handoff, and served its page and exact G2
identity over HTTP. OTA stopped cleanly and the original WebConfig, settings,
Admin ACLs, Pi network and host services were restored. The earlier radio CAD
warning remained unchanged; the earlier LAN upload failure's cause is still
unproven. The current GitHub unit workflow remains pending for this held release.
unproven. The GitHub unit workflow passed for source `c88885a3`; this release
remains held and unpublished.
The normal Full Heltec V4 Repeater from `c88885a3` passed two fresh-flash
connections through the unchanged native Chromium setup GUI. With power saving
on, its quiet console checks passed for 600 seconds open and closed with logging
off, and 600 seconds open with USB logging. See the [V4 native console
qualification](../test-results/hardware_validation_heltec_v4_native_console_2026-10-06.md)
for deadlines, host preparation and retained failed attempts. USB remained
attached, so these checks do not measure CPU sleep or button wake. The subsequent
V4 button-wake correction and its software validation are separate; physical
button wake was not tested here.
Build qualification does not establish physical compatibility on every board.
Hardware CDC has finite buffers, and closing a Linux TTY does not necessarily
@@ -92,8 +92,11 @@ its recovery ACLs, and its running MQTT services were not changed by this run.
## Remaining limits
The user's permanent/intermittent web-console hang has not been reproduced or
proven fixed. Current tests do not qualify every host OS, physical USB adapter,
cold power cycle, ESP32 board, or firmware profile. The final V4 build has not
been flashed through Chromium's remote physical Web Serial interface. Keep
those limits separate from the successful browser OTA and console idle checks.
The subsequent [current Full V4 native browser qualification](hardware_validation_heltec_v4_native_console_2026-10-06.md)
adds actual Pi Chromium Web Serial, the unchanged setup GUI's five-second
deadline, and measured fresh-flash and power-saving console idle results. The
older source and boundaries above remain historical evidence. Neither report
qualifies every host OS, physical USB adapter, cold power cycle, ESP32 board,
firmware profile, or radio traffic load. USB-attached console checks do not
establish CPU sleep, measured current or button wake; the subsequent V4
button-wake correction needs separate qualification.
@@ -0,0 +1,153 @@
# Heltec V4 Full native browser console - 2026-10-06
The normal Full Heltec V4 Repeater from source
`c88885a315208908e050c57fc0ec0a71ff10bd6f` passed fresh-flash connection,
600 seconds with the console open, and 600 seconds with it closed through the
unchanged `config.meshcore.io` setup GUI. Power saving was on and logging output
was off during those idle intervals. A separate fresh-flash and 600-second
open-console trial also passed with USB logging on. Both trials restored the
original settings and closed their streams. Final operator readback and owned
browser/host-service cleanup also passed. Release 1.17.1.9 remains held and
unpublished.
These results extend the [earlier startup investigation](hardware_validation_esp32_web_console_startup_2026-10-05.md)
to the current normal Full image and native hardware CDC. They are physical
console stability checks, not a measurement of sleep current, radio traffic
capacity, or every ESP32/host combination.
## Image and build gates
The requested target was `heltec_v4_repeater`, Full profile. Its resolved
`heltec_v4_repeater_observer_mqtt` feature base retains the normal repeater
update identity. Runtime readback had to match the complete version
`v1.17.1.9-halo-keymind-cascade-dev-c88885a3 (Build: 06-Oct-2026)`.
The source includes the OTA upload correction in `d2983762` and WiFi ownership
correction in `95913c53`. The GitHub unit workflow for the current source passed;
this does not replace the physical checks below or the final release matrix.
| Artifact | Bytes | SHA-256 |
| --- | ---: | --- |
| Full application | 2,097,528 | `1f10f44149b3f56edbe0ecb2a2bab9b51da651a6d047e2c2c1555691468d2b4c` |
| Merged image | 2,163,064 | `3264d7a1a5713755b5dd10ad1c81bb0588e4a072eeaa126b86307a42be77bb5b` |
| Matching ELF | - | `0545eef46da30a86f86531199a91e887167d463c5981f193c0455cd75d633538` |
The package hash, linked capability, and RAM gates passed, with 20,880 bytes
of margin above the required heap budget. The linked application includes
HWCDC, its retained-PHY handoff and startup owner hook; no TinyUSB USBCDC class
is linked. Full ESP-NOW, WiFi/LoRa OTA, and USB logging capability checks passed.
These are build checks; this run did not exercise those radio/OTA features.
Both application slots are `0x640000` bytes, at `0x10000` and `0x650000`.
The padded partition table SHA-256 is
`9af3af2b74e944337ba85f2b0027ee80df160579a1ab746ba0f95853f618cd60`.
The merged image contains that table and the exact audited application at
`0x10000`. The application fits either slot with 4,456,072 bytes of margin.
The merged artifact was audited, but the physical tests wrote the application
only; they do not qualify a factory erase or merged-image installation.
## Native browser boundary
Chromium `154.0.8037.92` ran on the Pi with native OS Web Serial ports and
streams. The actual Vue GUI and stock SerialCLI were loaded before flashing.
The stock DOM Connect flow after the stock flasher returned sent the normal
plain `time <epoch>` request. Its unchanged 5,000 ms timeout covers both native
write completion and the framed response. No additional reset, cable action,
post-flash settle, command retry, or keepalive was inserted to turn that
connection into a pass.
| Website source | SHA-256 |
| --- | --- |
| GUI `index.html` | `efd64d1b56d57988fc4108f3f84efc943ed4c42ade4e4dbf8139c46fb3c0476d` |
| GUI `src/gui.js` | `919034806e903fbc41e29a367a7d854837f6520f47aa34e5d0618f5dd3d29800` |
| GUI `lib/serial-cli.js` | `fbfbdcfa8bf1a372a0396d342e018ba8ee83d33b7b98c3a546fd37cd7e8ce870` |
| Flasher `js/flash.js` | `e9762a29f6a91f8e33736e02ff41a81cd49e7b96fe253a5d71cba7daefd46d5b` |
| Flasher `lib/esp32.js` | `2a896d5e520ea9b6ea9900223c2460df797c3b2287f441daa4e50168a50b17e6` |
Automation selected only the single preauthorized native device. Before each
write, actual ROM reads checked the partition table and valid OTA sequence/CRC,
then directed the application write to the verified active slot (`app1` at
`0x650000`). The stock loader transfer, reset and disconnect remained in place;
full erase was disabled. Phase instrumentation recorded existing calls without
adding hardware operations. This qualifies that stock flash backend and native
GUI connection, not the complete firmware-picker selection/erase flow.
Original public identity, the ACL response, and 15 checked saved preferences
were compared with the original private reference after flashing and cleanup.
Only the numeric channel-busy diagnostic counter was normalized in preference
comparison. No raw identities, credentials or console replies are published.
## Physical results
| Check | Result |
| --- | --- |
| Fresh flash, then first GUI connection with logging off | PASS. Plain time clock-set acknowledgement in 1,614.4 ms; complete GUI bootstrap and exact version/reference checks passed. |
| Power saving on, logging off, console open | PASS. 600.093 seconds with zero native command writes or keepalives. Uptime advanced 18 -> 619 seconds; the first post-idle core reply took 35.9 ms. A full DOM Disconnect/Connect reread acknowledged time in 23.6 ms. |
| Power saving on, logging off, console closed | PASS. 600.100 seconds with streams closed and zero writes. Uptime advanced 627 -> 1232 seconds. The first DOM reconnect time response took 33.2 ms; it was the recognized framed clock-cannot-go-backwards rejection, not a clock-set acknowledgement. Full GUI bootstrap and version checks still passed. |
| Separate fresh flash, power saving on, USB logging | PASS. Fresh plain time acknowledgement in 696.1 ms, then 600.101 seconds open with zero writes or keepalives. Uptime advanced 18 -> 618 seconds; first post-idle core reply took 40.4 ms. Full DOM reconnect time acknowledgement took 24.8 ms. |
| Both trial cleanups | PASS. Original power saving off, logging output USB, all 15 checked preferences, public identity and ACL response were restored. The GUI port, reader and writer closed. |
Uptime gates account for the surrounding query/GUI bootstrap time. None of the
three quiet intervals contains a native write event. Both trials recorded zero
native command errors and no GUI modem-signal calls.
Radio observations stayed in active RX with zero physical RX errors. The core
error bitmask changed from `0` to `8`, with zero received packets and one sent
packet. This is the configured receive-silence watchdog warning, consistent
with the quiet-channel recovery policy; it is not eight errors or a CPU reset.
Neither trial establishes error-free RF operation or a loaded radio soak.
## Retained setup failures and host qualification
Two preceding browser attempts remain failed and unqualified. Attempt 2 timed
out before metadata completion or any GUI command was recorded; its exact
flasher substage was not instrumented. Attempt 4 timed out in the stock loader
connection/reset loop after 674 USB JTAG reset entries. It did not reach loader
main return, application `writeFlash`, or a GUI command. Neither failure
exercised the GUI's five-second command timer, so they are not counted as
firmware console timeout failures or silently converted into passing trials.
Before the passing logging-off attempt, the host TTY was normalized from
`VMIN=0, VTIME=0` to `VMIN=1, VTIME=0`, with zero explicit modem-signal calls
in that preparation. No firmware or website code changed. That host setup is
part of the qualified configuration; the evidence does not isolate the original
ROM synchronization failure's cause. The second logging trial started without
another external CLI preflight or TTY change.
The pinned stock flasher also calls `connect(resetMode, romBaudrate)` from
`detectChip`, although the second `connect` argument is a retry count with a
default of seven. The supplied value is 115,200. This explains the excessive
retry budget after failed synchronization, not why synchronization first failed.
It is an external flasher issue; no website patch was required for the passing
GUI tests and no website deployment is claimed.
## Evidence and limits
Final read-only operator verification again matched the exact version, original
15 checked preferences, public identity and ACL response; uptime was 686 seconds
and flags remained `8`. The owned Chromium profile and tunnel were closed, the
serial port had no owner, and original host service states were restored:
ModemManager and mctomqtt active, mesh-logger inactive. No Pi network
configuration changed. Restored service state does not establish MQTT delivery.
The compact [safe audit](hardware_validation_heltec_v4_native_console_2026-10-06.safe.json)
contains artifact/helper/report hashes and selected outcome fields. Completed
sanitized reports and phase observations are retained privately; raw reference
settings remain private. Failed attempts are retained separately from the
qualified results.
The USB cable remained attached throughout these trials. The native-host sleep
guard keeps CPU command service available while the USB host is connected,
including when its serial port is closed. Power saving on is therefore not
proof of CPU light-sleep entry, measured current, or physical button wake.
At this source, V4 lacks the momentary-button wake profile flag. The later
profile correction is outside this image and needs separate software/build
and console validation. No physical button access is available in this setup,
so physical button wake remains unqualified.
These checks qualify this historical `c88885a3` Full application, native Pi Chromium setup,
protected active-slot application flash, and measured intervals. They do not
qualify every host OS, board/role, adapter, cold power cycle, factory erase,
heavy logging volume, measured power consumption, or RF traffic load. GPIO/reset
behavior outside the unchanged stock flasher's reset sequence was not separately
qualified. The release remains unpublished; the final same-source all-board
build and release packaging are separate gates.
@@ -0,0 +1,240 @@
{
"schema_version": 1,
"scope": "Heltec V4 normal Full native Chromium stock config GUI; active-slot app-only flash and quiet USB stability",
"source_commit": "c88885a315208908e050c57fc0ec0a71ff10bd6f",
"firmware_version": "v1.17.1.9-halo-keymind-cascade-dev-c88885a3 (Build: 06-Oct-2026)",
"status": "qualified_historical_c888_USB_stability_operator_restored",
"build": {
"target": "heltec_v4_repeater",
"profile": "full",
"platformio_feature_base": "heltec_v4_repeater_observer_mqtt",
"normal_update_identity_retained": true,
"app_filename": "heltec_v4_repeater-full-usb-wifi-ota-v1.17.1.9-halo-keymind-cascade-dev-c88885a3.bin",
"app_sha256": "1f10f44149b3f56edbe0ecb2a2bab9b51da651a6d047e2c2c1555691468d2b4c",
"app_bytes": 2097528,
"merged_filename": "heltec_v4_repeater-full-usb-wifi-ota-v1.17.1.9-halo-keymind-cascade-dev-c88885a3-merged.bin",
"merged_sha256": "3264d7a1a5713755b5dd10ad1c81bb0588e4a072eeaa126b86307a42be77bb5b",
"merged_bytes": 2163064,
"elf_sha256": "0545eef46da30a86f86531199a91e887167d463c5981f193c0455cd75d633538",
"partition_table_4096_sha256": "9af3af2b74e944337ba85f2b0027ee80df160579a1ab746ba0f95853f618cd60",
"slot_bytes": 6553600,
"usb_mode": 1,
"cdc_on_boot": 1,
"hwcdc_phy_handoff_compiled": true,
"ram_gate_passed": true,
"heap_margin_bytes": 20880,
"capability_gate_passed": true,
"verified_capabilities": [
"bridge.espnow",
"logging.usb.output",
"ota.update.lora",
"ota.update.wifi",
"profile.full"
],
"independent_build_audit_sha256": "34d1bed7549f9457063370c1a7703a2c6cd7d75b9f2a8cb3308d000bf33c56f3"
},
"helpers": {
"original_driver_sha256": "460b217824c897daefc377247562330290fb61e67facdf9ed3d9283160c5ca78",
"phase_driver_sha256": "20797d40df1b0e2f30abd5779b3c1006173974cf71404b0d6e1932c721492d84",
"phase_derivative_adds_no_hardware_operations": true
},
"host_preparation": {
"tty_before": {
"VMIN": 0,
"VTIME": 0
},
"tty_after": {
"VMIN": 1,
"VTIME": 0
},
"explicit_modem_signal_calls": 0,
"scope": "Host TTY VMIN/VTIME only; no firmware or browser JS changes"
},
"failed_attempts": [
{
"attempt": 2,
"report_sha256": "60640f0bdd6b2f91d278bc3ffdc3a372ec799f8f057674437432ab091b7d302c",
"qualified": false,
"error_name": "TimeoutError",
"duration_seconds": 303.30445313453674,
"website_command_sent": false,
"website_five_second_deadline_exercised": false,
"application_write_not_reached_proven": false,
"stock_usb_jtag_reset_entries": null,
"initial_sync_failure_cause": "not established"
},
{
"attempt": 4,
"report_sha256": "9ab397494c011d1828d46aefbc2b927b36ad81160d92ec11a14dedb3f280f1ae",
"qualified": false,
"error_name": "TimeoutError",
"duration_seconds": 277.81140637397766,
"website_command_sent": false,
"website_five_second_deadline_exercised": false,
"application_write_not_reached_proven": true,
"stock_usb_jtag_reset_entries": 674,
"initial_sync_failure_cause": "not established"
}
],
"external_flasher_library": {
"sha256": "2a896d5e520ea9b6ea9900223c2460df797c3b2287f441daa4e50168a50b17e6",
"default_connection_retry_count": 7,
"stock_detectChip_supplies_rom_baudrate_as_retry_count": 115200,
"scope": "Excessive failed-reset budget in stock flasher; not a MeshCore firmware fix or root-cause proof for initial sync failure."
},
"pending_physical_trials": [],
"radio_scope": "USB stability qualification records CAD and receive-silence watchdog flags as warnings; requires physical RX errors zero and rejects queue-full/start-RX/unknown flags. No zero-error radio claim.",
"limitations": [
"Current-source normal Full HWCDC application; not released-stock migration proof.",
"Host TTY VMIN/VTIME normalization is part of qualified host setup.",
"Failed flasher preparations remain failed and did not exercise the GUI five-second command timer.",
"Merged artifact is audited; actual browser tests flashed only the protected active application slot.",
"No measured sleep current, factory erase/cold-cycle, heavy logging or RF traffic-soak claim.",
"USB-attached CPU sleep guard means no physical light-sleep/button-wake or current-draw qualification; V4 momentary-button wake profile correction needs new-source qualification.",
"Original host service states restored; MQTT packet delivery was not measured."
],
"qualified_trials": [
{
"attempt": 5,
"logging_output": "off",
"power_saving": "on",
"report_sha256": "e4d49f17971d980565ad351b9b49a2e19c5ca8a83ac91805b87bf85ada49223e",
"independent_review_sha256": "0e60290e3d63b393bf6c8647bb6c966f7a015bdd09067ab0cf7840290201c9ea",
"first_plain_time_ms": 1614.4000000003725,
"first_plain_time_clock_set_acknowledged": true,
"native_command_deadline_ms": 5000,
"quiet_intervals": [
{
"kind": "open",
"requested_seconds": 600,
"actual_seconds": 600.0930547369935,
"native_writes_during_idle": 0,
"uptime_before": 18,
"serial_open": true,
"uptime_after": 619,
"uptime_delta": 601,
"uptime_continuity_verified": true,
"ok": true
},
{
"kind": "closed",
"requested_seconds": 600,
"actual_seconds": 600.100213859987,
"native_writes_during_idle": 0,
"uptime_before": 627,
"serial_open": false,
"uptime_after": 1232,
"uptime_delta": 605,
"uptime_continuity_verified": true,
"ok": true
}
],
"first_post_open_idle_core_ms": 35.90000000037253,
"post_open_idle_DOM_clock_reply_ms": 23.59999999962747,
"post_closed_idle_DOM_clock_reply_ms": 33.19999999925494,
"post_closed_idle_clock_set_acknowledged": false,
"post_closed_idle_clock_response": "recognized framed clock-cannot-go-backwards rejection",
"full_GUI_rereads_passed": true,
"native_command_errors": 0,
"GUI_modem_signal_calls": 0,
"original_restoration_verified": true,
"checked_preferences_count": 15,
"identity_ACL_response_and_checked_preference_hashes_preserved": true,
"original_power_saving": "off",
"original_logging_output": "usb",
"streams_closed": true,
"radio": {
"initial_core_error_flags": 0,
"final_core_error_flags": 8,
"all_observations_active_RX": true,
"physical_rx_errors_max": 0,
"final_recv": 0,
"final_sent": 1,
"scope": "Receive-silence watchdog8 warning after quiet; not a CPU reset or zero-error/traffic radio qualification."
}
},
{
"attempt": 3,
"logging_output": "usb",
"power_saving": "on",
"report_sha256": "13202bf1d378128c2ac0d99404aac2a877b3ef3e9e14cfc0fc7a727d71aeb1ee",
"independent_review_sha256": "0ab2d0c2d24e025fb381fedeebe5ee267b805c57f7ad6ec6a173a98201cacc34",
"first_plain_time_ms": 696.0999999996275,
"first_plain_time_clock_set_acknowledged": true,
"native_command_deadline_ms": 5000,
"quiet_intervals": [
{
"kind": "open",
"requested_seconds": 600,
"actual_seconds": 600.1005607169936,
"native_writes_during_idle": 0,
"uptime_before": 18,
"serial_open": true,
"uptime_after": 618,
"uptime_delta": 600,
"uptime_continuity_verified": true,
"ok": true
}
],
"first_post_open_idle_core_ms": 40.40000000037253,
"post_open_idle_DOM_clock_reply_ms": 24.800000000745058,
"post_open_idle_DOM_clock_set_acknowledged": true,
"full_GUI_rereads_passed": true,
"native_command_errors": 0,
"GUI_modem_signal_calls": 0,
"original_restoration_verified": true,
"checked_preferences_count": 15,
"identity_ACL_response_and_checked_preference_hashes_preserved": true,
"original_power_saving": "off",
"original_logging_output": "usb",
"streams_closed": true,
"radio": {
"initial_core_error_flags": 0,
"final_core_error_flags": 8,
"all_observations_active_RX": true,
"physical_rx_errors_max": 0,
"final_recv": 0,
"final_sent": 1,
"scope": "Known CAD/receive-silence flags are warnings; no zero-error RF or traffic-soak claim."
}
}
],
"browser": {
"version": "154.0.8037.92",
"native_OS_Web_Serial": true,
"stock_GUI_command_timeout_ms": 5000,
"GUI_sources": {
"index.html": "efd64d1b56d57988fc4108f3f84efc943ed4c42ade4e4dbf8139c46fb3c0476d",
"src/gui.js": "919034806e903fbc41e29a367a7d854837f6520f47aa34e5d0618f5dd3d29800",
"lib/serial-cli.js": "fbfbdcfa8bf1a372a0396d342e018ba8ee83d33b7b98c3a546fd37cd7e8ce870"
}
},
"operator_cleanup": {
"ok": true,
"exact_runtime_version_verified": true,
"checked_preferences_identity_ACL_response_preserved": true,
"uptime_seconds": 686,
"core_error_flags": 8,
"serial_closed_and_no_owner": true,
"owned_browser_profile_closed": true,
"owned_tunnel_closed": "parent independently verified",
"original_services_restored": {
"ModemManager": "active",
"mctomqtt": "active",
"mesh-logger": "inactive"
},
"network_configuration_changed": false,
"operator_report_sha256": "d6d6e03ad5058e1257a6653af70203a3852dace47701220d9fd5c5020c56ae33",
"services_report_sha256": "f02ab0773cc35c11c2c9f400d8dffe0e7dd1807a09ebd942ee652ccc82c0f420",
"browser_cleanup_report_sha256": "d22a76bffa71dac46ab9514f066369047642e8c0bdeda36c380e133b724ca299"
},
"sleep_scope": {
"USB_cable_remained_attached": true,
"native_host_sleep_guard_present": true,
"CPU_light_sleep_entry_measured": false,
"current_draw_measured": false,
"physical_button_wake_tested": false,
"c888_V4_momentary_button_wake_profile_flag_present": false,
"new_button_fix_qualification": "pending"
}
}