Skip to content

feat(bt): non-blocking scan, Free3-R profile, debug-capture flag#41

Merged
imshentastic merged 10 commits into
mainfrom
feat/bt-scan-ux-debug-capture
Jun 23, 2026
Merged

feat(bt): non-blocking scan, Free3-R profile, debug-capture flag#41
imshentastic merged 10 commits into
mainfrom
feat/bt-scan-ux-debug-capture

Conversation

@imshentastic

Copy link
Copy Markdown
Owner

Summary

BLE page-turner improvements, driven by bringing up the Free3 remote on the X3.

  • Non-blocking device scan. startScan() previously called delay(durationMs), freezing the whole UI for the full scan (~10 s) so "Scan for Devices" looked hung. It now starts asynchronously (NimBLE auto-stops and fires onScanEndonScanComplete), so the UI stays live.
  • Animated "Searching…" + named-first ordering. The device-list screen animates trailing dots while scanning (throttled to avoid strobing the e-ink panel), and scan results are sorted so named devices sit above "Unknown" (then by RSSI). The live list is guarded against the async-scan mutation that now happens on the NimBLE host task.
  • ENABLE_BT_DEBUG_MONITOR build flag. Added to [base] build_flags so the in-app "BT Debug Capture" menu and the BTDBG raw-HID hex dump (previously compiled out) are available for bringing up new remotes.
  • Free3-R page-turner profile + exact-name matching. Adds a profile for the Free3 in R+sound mode (keycode in byte[0], 0x01=back / 0x02=forward). Fixes findDeviceProfile() to try exact device-name matches before the fuzzy substring patterns — without this, the mode-specific name Free3-R was hijacked by the broad Free3 pattern and mapped to the wrong Free3-M (byte[2]) layout.

Testing

  • Builds clean on tiny-bitter (Flash ~82%).
  • On-device: Free3 in R+sound connects, matches the Free3-R profile by exact name (byte[0]), and LEFT/RIGHT turn pages back/forward in the reader. Scan screen shows the animated indicator and no longer freezes the UI.

Notes

  • The left button's third code (0x07) is intentionally left unmapped here; a follow-up will bind it to a manual e-ink refresh.
  • Unrelated: under very low heap the X3 can still crash in NimBLE teardown on disconnect (HCI reason 520). Pre-existing, tracked separately.

…modal (4.5.4)

Field feedback: the Optimize Selected modal felt dead during long
phases (image WASM pass + chapter prebake) -- the status line read a
static 'Optimizing FOO... (1/1)' the whole time with no percentage
visible anywhere, so users couldn't tell if it was still working vs
silently stalled.

Now every progress callback (download / convert / upload / prebake)
threads through a single setPhase() helper that always shows current
phase + overall %, plus MB-of-MB during the upload and prebake's
status string when present. Heartbeat is continuous instead of sticky.

Also: hide the inherited 'Select a file to upload to ...' subtitle
during Optimize Selected (it's wrong for that flow -- source is the
device, not disk) and restore it for the next fresh upload.

Version bump 4.5.3 -> 4.5.4.
…nt timeout (4.5.4)

LAN-OTA: when the device asks for a heap-defrag restart mid-upload the
retry loop previously just slammed a dead socket every 2s for the
whole restart+wifi-recovery window (~13s), exhausting attempts before
the device was even back. Now: detect 'restart'/'fragment' in the
error, switch to /api/status probe-loop (2s polls, 1.5s timeout each,
up to 60s total) so we wait until the device confirms it's serving
again, THEN reconnect. Server's RESUME protocol picks up at the saved
byte offset. Status text shows 'Device is restarting (3.4/6.2 MB
saved). Waiting...' -> 'Waiting for device to finish restart... (Ns)'
-> 'Device back online. Resuming upload from 3.4 MB...' so the user
can see they're not stuck. File object stays in closure across all
retries -- no re-pick needed.

Misleading post-install text replaced. 'This page will reload when
vX.Y.Z is detected' was a lie when the device boots into Home (no FT
server, /api/status 404s forever). New text tells the user to re-enter
FT manually to verify.

Prebake: SD font fetch had no timeout, so a hung /api/fonts/file
response silently wedged the whole prebake at 'fetching SD font' with
no progress and no error. Now 30s deadline + 3 attempts with
backoff. WASM callMain blocks the JS main thread so setInterval can't
tick during it -- hook Module.print/printErr to nudge the progress bar
+0.4% per stdout line (capped at 89%) so 80->100% actually moves.

New 'Prebake Selected' button on FilesPage skips download-only
re-upload steps and runs ONLY chapter prebake on the already-optimized
SD copy. Lets users recover from a stalled prebake in half the wall
time of a full Optimize Selected re-run.
…prebake diff

Four field-reported fixes folded into local 4.5.4 test build:

1. WS upload (book + cpfont) now detects 'Heap too fragmented' /
   'WebSocket closed' mid-stream and probes /api/status until the
   device confirms it's serving again before retrying. Same UX shape
   the OTA path got in the prior commit -- 'Device is restarting,
   waiting...' / 'Device back online, resuming' instead of silent
   retry that read as 'stuck at 29%'.

2. Remove standalone 'Prebake Selected' button next to 'Optimize
   Selected' that user said was unclear. Replaced with in-modal
   Resume button that shows ONLY when a previous run left recoverable
   state. Tracks per-item upload-back success so the Resume run
   correctly picks prebakeOnly mode when the SD copy is already
   optimized, vs full pipeline replay otherwise.

3. loadRecentCovers (CAROUSEL covers) showed the Loading popup but
   never set homeRenderPopupShown -- the 4.5.3 end-of-render fix only
   covered the loadShelfCovers (BOOKSHELF covers) path. Now sets the
   flag + requestUpdate, matching the shelf path. Fixes 'Loading
   sits on top of carousel after fresh-flash reboot until a button
   press clears it'.

4. 'Settings differ from prepared layout' prompt now lists exactly
   which fields differ (Font / Hyphenation / Line spacing / etc.)
   with device-vs-prebake values. The generic prior text gave users
   no way to align device settings to match the prebake without
   trial-and-error toggling.
Mirrors the BT settings pre-flight + silent-restart-to-self pattern
shipped in 4.5.3. Both KOReaderAuthActivity (WiFi + HTTPS sync auth)
and OpdsBookBrowserActivity (WiFi + HTTPS feed fetch + XML parse)
need ~66 KB free + ~48 KB MaxAlloc which a mid-reading session
rarely has -- users used to see 'Memory low. Restart device.' and had
to power-cycle. Now: onEnter pre-flights, silent-restarts to self,
boots back on a clean ~150 KB heap, continues.

Two new SILENT_REBOOT_TARGETs (6, 7); bound widened so they don't
silently snap to Home; ActivityManager gets goToKoreaderAuth();
SilentRestart.h declares both new functions + extern post-boot
guard flags (g_postKoreaderSilentReboot / g_postOpdsSilentReboot)
that the activities check to avoid an infinite restart loop if
even a fresh boot can't hit the floor.

Also bundles the in-flight 4.5.4 work from this session:
- Generalized in-modal Resume button (replaces standalone Prebake
  Selected) that adapts label to phase + restores selection state
- WS upload restart-aware UX: probe /api/status during heap-restart
  windows + clear 'Re-enter FT then click Resume' message after
  60s of silence (typically means device panic-rebooted to Home)
- RTC ftUploadInProgressFlag for auto-restart-to-FT after panic,
  with consecutive-fail counter guard
- loadRecentCovers now sets homeRenderPopupShown so carousel
  Loading clears without needing a button press (matches the
  loadShelfCovers fix shipped in 4.5.3)
- 'Settings differ from prepared layout' prompt lists exactly
  which fields differ (Font/Hyphenation/Line spacing/etc.)
- Optimizer prebake heartbeat + SD-font fetch timeout/retry
… BT prompt

Two follow-ups to the 4.5.4 'list which fields differ' work:

1. The open-book prompt was dumping raw uint32 fontId hashes
   ('70596465 -> 1849723397') because that body builder was written
   before the BT-path one got the fontLabel() helper. Useless to users.
   Now uses fontLabel() too -- 'Bitter 14pt -> LXGWWenKai 18pt'
   matching the BT-path prompt's display. PrebakeManifest already
   carries fontFamily/fontSize/sdFontFamilyName for exactly this.

2. Open-book prompt + BT-connect prompt could both fire back-to-back
   when a BT remote was connected on a book that had both PrebakeManifest
   AND PxcManifest fingerprints. Slightly different wording confused the
   user even though they'd just answered 'Restore prepared layout'.
   When prebakePromptStage_ != -1 (user answered the open-book prompt
   this session), suppress the BT prompt. The open-book 'Restore'
   handler already applies fontFamily/fontSize/sdFontFamilyName, so
   any residual mismatch the BT check would surface is from a missing
   SD font -- which the BT prompt can't fix either.
Per user feedback: 'Resume button should be inside the modal, don't put
resume on the main screen behind the modal.' The banner's per-file
Resume called resumeSingleUpload() which only re-pushed the raw file
bytes via uploadFileWebSocket -- it didn't re-run the Optimize pipeline.
So when a user's Optimize Selected failed mid-prebake and they clicked
the banner Resume, the file was already on SD, nothing visible
happened, and the user thought Resume was broken. Banner now exposes
only 'Retry from start' which reopens the upload modal, where the
in-modal Resume button is the canonical resume path.
Two BT setup UX fixes for 4.5.4:

1. Hitting Scan when BT is off no longer bounces with 'Enable BT first.'
   Users perceived that as 'scan turned BT off' since the toggle label
   flipped back to Enable. Now Scan one-taps through: enable BT (with
   the same silent-restart-to-recover heap path the toggle uses), then
   start the scan. Persists SETTINGS.bluetoothEnabled=1 so the post-
   restart auto-restore lands with BT on.

2. Successful connect status changed from misleading 'Bluetooth
   enabled' (BT was already on by definition at that point) to
   'Connected: <remote-name> (saved)'. The meaningful state change at
   that moment is the bond, not the BT enable. Truncates long device
   names at 24 chars so the bottom-line status doesn't overflow.
…try from start' to 'Retry'

Field report: chapter-cache upload step shows 'uploading cache file
12/703' for ~60s before ticking to 13/703 -- the numeric progress is
real but the cadence makes the modal look frozen.

Two visual aliveness signals during in-progress runs:
- CSS shimmer in the progress bar's filled portion (white gradient
  sliding L->R every 1.6s). Pure CSS, no JS tick cost.
- Animated 1->2->3 ellipsis appended to the status-text line via a
  CSS @Keyframes content-swap. Reads as 'still working' even when the
  body text is unchanged.

Both classes (#progress-fill.done, #progress-text.pulsing) toggle in
the upload-modal-open and terminal-state branches (Optimize Selected
flow + regular file-upload flow + cancel branch).

Also rename the failed-uploads banner button 'Retry from start' to
'Retry'. The tooltip explains it reopens the modal where the in-modal
Resume button picks up at the saved byte offset (i.e. it doesn't
actually retry from scratch, naming was misleading).
- Scan was a blocking delay() that froze the UI ~10s; make it async via
  NimBLE duration + onScanEnd/onScanComplete to clear scanning state
- Animate "Searching..." dots; guard the device list against mid-scan
  mutation on the BLE host task (render + input)
- Sort scan results: named devices above "Unknown", then by RSSI
- Add ENABLE_BT_DEBUG_MONITOR flag exposing BT Debug Capture + BTDBG raw HID dump
Add a Free3-R profile (R+sound mode): keycode in byte[0], 0x01=back,
0x02=forward, with clean 0x00 releases handled by the standard decode.

Fix findDeviceProfile() to try exact device-name matches before the
fuzzy substring patterns. Without this, a mode-specific name like
'Free3-R' was hijacked by the broad 'Free3' pattern and mapped to the
wrong Free3-M (byte[2]) report layout.
@imshentastic imshentastic merged commit a4c5507 into main Jun 23, 2026
0 of 3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant