Skip to content

feat: BLE text mirror (Chat template) behind CROSSPOINT_MIRROR - #11

Open
ynsgnr wants to merge 39 commits into
developfrom
claude/elegant-shannon-i9uuis
Open

ynsgnr wants to merge 39 commits into
developfrom
claude/elegant-shannon-i9uuis

Conversation

@ynsgnr

@ynsgnr ynsgnr commented Nov 2, 2026 •

Copy link
Copy Markdown
Owner

Summary

  • Goal: a Mirror screen that shows a phone's chat (messages plus the draft being typed) on the e-ink display over BLE, plus an opt-in BLE keyboard that unlocks the phone (PIN pad, touch pattern) and starts the mirror with a key chord. Device side of ynsgnr/retink (Android app, protocol 3).
  • Where: new mirror/ directory. Upstream files touched are listed in mirror/MIRROR_CHANGES.md: platformio.ini (one extra_configs line), HomeActivity and CoverGridHomeUi (Home menu entry), and the CI workflows. Everything is behind -DCROSSPOINT_MIRROR=1, set only by the mirror (X4, C3) and x4pro-mirror (X4 Pro, S3) environments.
  • Security: every characteristic and the notification subscription need an encrypted, authenticated link; LE Secure Connections only with a passkey shown on screen; resolvable private address and a generic name; an unknown phone can connect only during the 2-minute "Pair phone" window; a failed pairing deletes its bond. See mirror/README.md.
  • Keyboard (off by default): hold Left on the waiting screen. One bond serves the mirror and the keyboard. Opening the unlock screen wakes the phone; the PIN pad and the pattern screen send each key at once and keep nothing (pattern dots send digits 0-8, lifting the finger sends Enter). The trigger chord (default Ctrl+Alt+Shift+M) is edited on the device (hold Right) and announced to the phone.
  • CI: mirror.yml builds both mirror environments and uploads firmware-mirror.bin and firmware-x4pro-mirror.bin as artifacts, runs the host tests, and checks the generated files. The last commit pins the cppcheck job to ubuntu-24.04 (the runner image dropped libpcre3); it is fork-only.

Scope Check

This is a personal fork feature, outside upstream CrossPoint scope (SCOPE.md), and is not intended for upstream.

  • I have read SCOPE.md and ROADMAP.md.
  • Not a new theme. Not a network connector, writing tool, browser or PDF feature.
  • Touches no freeink-sdk/, lib/hal/, bootloader, OTA or recovery code.

Additional Context

  • Verified locally: 266 host checks (including 9 Kotlin-recorded transcripts replayed byte for byte), pio run -e mirror and -e x4pro-mirror, the retink Kotlin tests and APK build.
  • Not verified: nothing has run on hardware: pairing and the passkey, the sniffer check that nothing is readable unpaired, HID typing on a lock screen, wake, the pattern lock, the chord, refresh quality, heap. The ordered checklist is in mirror/docs/FINDINGS.md.
  • Known gaps: the device has no lock of its own (anyone holding it can pair, forget the phone or type keys); the app's keyboard switch is app-level and Android keeps its HID link.
  • The firmware-handoff process (independent reviews, human acceptance, hardware testing) has not been done.

AI Usage

Did you use AI tools to help write this code? YES (Claude Code wrote the change; a human has not reviewed it yet).

🤖 Generated with Claude Code

https://claude.ai/code/session_01Xxo1zUj9y971kfgC3syQCA

New mirror/ directory: portable protocol core, Chat renderer, host
simulator and tests, NimBLE peripheral and Mirror activity.
Upstream hooks are listed in mirror/MIRROR_CHANGES.md.

Verified: host tests (234 checks), pio run -e x4pro and x4pro-mirror.
Not verified: hardware, X4 (C3) build. See mirror/docs/FINDINGS.md.
Authenticated pairing (disconnect unauthenticated peers), connection epoch
reset, single resync per gap, render lock released before the panel
refresh, layout split with empty state, scroll marker and block-aligned
own messages, single-op diffs applied in place. CI builds both mirror
environments with the upstream toolchain recipe and uploads the images.
Adds the X4 (C3) build numbers.
@github-actions

github-actions Bot commented Nov 2, 2026 •

Copy link
Copy Markdown

Firmware builds

Firmware is not available for the latest commit of this PR (74be15c). The build did not produce firmware.bin, firmware-sticky.bin, firmware-x4c.bin, firmware-papermono.bin, firmware-metalio_eink4.bin, firmware-eego_a4.bin, firmware-picco.bin. View the CI run.

…indicator

Status band with dark-mode state and Confirm toggle, status/command
handling in Session, BandedGfx and shared frame composition, template
dump() and glob-based sources so a new template needs no common-layer
change, shape fixes from review. Adds the control golden transcript.
cppcheck flagged ICONS[7] as out of bounds: the lookup loop could end at
index 7. Start from a valid fallback and stop at the first match.
The custom-sdkconfig envs run an Arduino core build in a dummy project
that keeps build_src_filter but drops build flags and lib_deps. Mirror
sources in that phase pulled in the hal library and Rtc failed to
compile (BoardConfig.h not found). mirror/build.py now sets the source
filter lazily and leaves the mirror folders out during that phase.
Reproduced from a pristine IDF libs cache; both mirror envs build.
…dow, protocol 3

Every characteristic and the notification subscription need an encrypted, authenticated link. LE Secure Connections only, MITM passkey, resolvable private address, generic name. Unknown phones can connect only while Pair phone is open; a bonded phone cannot re-pair outside it. Bonds from the old security model are deleted. Protocol 3 adds unlock/HID fields (unused until the keyboard lands).

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Xxo1zUj9y971kfgC3syQCA
One bond serves the mirror and an opt-in HID keyboard built with authenticated characteristics only. Unlock pad sends each key immediately and keeps nothing; the phone reports only a locked boolean. Chord (default Ctrl+Alt+Shift+M) is edited on the device, validated in core/Chord, and announced to the phone. Docs list what needs hardware verification.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Xxo1zUj9y971kfgC3syQCA
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Xxo1zUj9y971kfgC3syQCA

ynsgnr commented Nov 4, 2026

Copy link
Copy Markdown
Owner Author

CI status for bc8bf8b:

  • clang-format: my formatting was off (checked with clang-format 21). Fixed in 646a063.
  • cppcheck: not caused by this PR. The job fails before cppcheck runs: the ubuntu-latest image is now Ubuntu 26.04 ("resolute") and apt-get install libpcre3 returns E: Unable to locate package libpcre3. It needs a change to the "Install cppcheck runtime dependency" step in .github/workflows/ci.yml (upstream's file, which this PR does not touch), so I have not changed it here. cppcheck will stay red until that step is fixed or the runner is pinned to an older Ubuntu. I cannot re-run cppcheck locally for the same reason (the pioarduino binary needs libpcre.so.3).

Generated by Claude Code

claude added 14 commits November 4, 2026 15:34
Opening the unlock screen wakes the phone. Pattern dots send digits 0-8 as the finger touches them and Enter when it lifts; the PIN pad keeps an Enter key. The path is never drawn.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Xxo1zUj9y971kfgC3syQCA
… docs)

drain keeps the reserved inbound capacity, the connection epoch changes once per link, a failed authentication deletes the bond and closes the pairing window, refused links skip connection tuning. Clearer messages, periodic full refresh on the unlock screens, no key highlight on touch boards. README trimmed.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Xxo1zUj9y971kfgC3syQCA
PlatformIO's cppcheck binary links libpcre.so.3; ubuntu-latest is now 26.04, which has no libpcre3 package. Fork only: drop this commit when sending the work upstream.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Xxo1zUj9y971kfgC3syQCA
…omments

Unlock, pattern and chord screens move to MirrorScreens.cpp. Per-link state is one struct, the chord rows are a table, BLE security setup is one function, the security epoch lives with the other settings.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Xxo1zUj9y971kfgC3syQCA
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Xxo1zUj9y971kfgC3syQCA
The list Home cannot scroll, so with Plugins/OPDS and Sync Now the Mirror row fell below the screen. In mirror builds it now lists Library, Sync Now, Mirror and More...; File Browser, File Transfer, OPDS/Plugins and Settings open from More.... Cover-grid Home and stock builds are unchanged.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Xxo1zUj9y971kfgC3syQCA
…pairing logs

The X4 Pro has no Confirm button by default, so pairing could never be started there. The Mirror screen is now a column of buttons (tap on touch boards, cursor plus Confirm elsewhere); Forget and Keyboard ask for a second tap. Chat view: side buttons or swipe scroll, tapping the top toggles dark mode. Logs for the pairing window, advertising, connections and authentication; advertising is no longer restarted while a central is connecting.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Xxo1zUj9y971kfgC3syQCA
… screen

A GATT server that fails to start (for example with the keyboard service) now ends in 'Bluetooth could not start' instead of a silent non-advertising device. The Mirror screen shows whether the radio is advertising, so pairing problems are visible without a serial cable.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Xxo1zUj9y971kfgC3syQCA
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Xxo1zUj9y971kfgC3syQCA
…NimBLE errors

With the keyboard on, advertising failed to start on hardware (serial log: 'advertising FAILED to start'). The retry drops the HID UUID and appearance from the advertisement; NimBLE error logging is on in the mirror builds so the reason code shows in the serial log.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Xxo1zUj9y971kfgC3syQCA
…onse

Serial log from the X4 Pro: advertising with the private address fails (rc=530, HCI 0x12) and the public address works. The device name did not fit the main advertising packet.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Xxo1zUj9y971kfgC3syQCA
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Xxo1zUj9y971kfgC3syQCA
The device slowed its CPU and could auto-sleep while advertising for a phone to pair; only a connected phone kept it awake.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Xxo1zUj9y971kfgC3syQCA

ynsgnr commented Nov 4, 2026

Copy link
Copy Markdown
Owner Author

CI note for c0a91cc: Build mirror (the X4 / ESP32-C3 image) failed before compiling. Its toolchain download, esp-idf-v5.5.5.tar.xz from the pioarduino GitHub releases, returned HTTP 403 on all 5 retries. That points at a GitHub download or rate limit on the runner and not at this change (the commit only changes MirrorActivity.h, and the X4 Pro build Build x4pro-mirror compiles it). There is no code fix to port. I tried a re-run but the workflow is still running, so I will re-run the failed job once the run completes.


Generated by Claude Code

The pairing code disappears within a second on hardware; the log did not say who ended the link.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Xxo1zUj9y971kfgC3syQCA
Phone log: with the keyboard appearance the phone classifies the device as a keyboard (class 0x540), shows its own PIN and answers the passkey request with it, so the code on our screen never matched.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Xxo1zUj9y971kfgC3syQCA
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Xxo1zUj9y971kfgC3syQCA
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Xxo1zUj9y971kfgC3syQCA
The stack keeps the callback objects, which held a reference to the first MirrorBle. After leaving and re-entering Mirror, a connecting phone called into the destroyed instance and the mutex lock aborted the firmware (decoded backtrace: MirrorBle::onConnected line 261).

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Xxo1zUj9y971kfgC3syQCA
claude added 12 commits November 5, 2026 08:30
…sing bond

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Xxo1zUj9y971kfgC3syQCA
…imeout, heap log on stop

Lessons from Flowe OS's BLE service: deinit while a link is closing races the host task; heap per visit should be watched.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Xxo1zUj9y971kfgC3syQCA
…lock, long-press scroll, steadier pairing

Chat schema 2: draft_cursor, SetDraftCursor, ScrollTop/ScrollBottom (regenerated, goldens updated). The draft box grows with the text and shows the cursor's line. The pattern is collected while dragging (samples interpolated) and sent when the finger lifts; opening Unlock sends Enter after the wake when the phone is locked. Holding a scroll button jumps to the oldest or newest message. The device accepts a connection whenever a bond exists, drops other bonds only during a fresh pairing, advertises while connected so the phone's companion scan can find it, and refuses a second central.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Xxo1zUj9y971kfgC3syQCA
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Xxo1zUj9y971kfgC3syQCA
The mirror workflow ran on push and on pull request for the same commit; it now runs on pull request (push only for develop) and cancels superseded runs. The X4 (C3) image builds on demand only. The stock CI builds only the X4 Pro instead of eight boards.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Xxo1zUj9y971kfgC3syQCA
…ding security request

While a phone is linked the device advertises non-connectable (the companion scan still sees it) instead of connectable, so the phone's HID host and app are not refused and retried in a loop. The security request now waits 1.5 s and is sent only if the phone has not secured the link itself.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Xxo1zUj9y971kfgC3syQCA
Serial log: a bonded phone connected, encryption failed (MIC failure) and the failure handler deleted the bond, so the next start showed 0 bonds. Only a pairing in progress deletes a bond now. Connection tuning waits for a secure link and the device no longer sends its own security request (it preceded HCI ack timeouts and host resets).

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Xxo1zUj9y971kfgC3syQCA
…ng, diff validation)

A readable-NVS check before deleting bonds, a random code for a refused pairing, a pairing that outlasts the window, events of a refused link ignored, a cached bond count for the render task, Enter after the wake without blocking, buttons inactive while the passkey shows, second-tap state reset, a single-op diff validated before it is applied, UTF-8 hard breaks clamped, periodic full refresh on the status screen. Lessons and the open findings are documented.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Xxo1zUj9y971kfgC3syQCA
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Xxo1zUj9y971kfgC3syQCA
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Xxo1zUj9y971kfgC3syQCA
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.

2 participants