Send web articles to an M5Stack PaperS3 (4.7" e-ink, ESP32-S3) for offline reading. It works like Send-to-Kindle: share from your phone → the cloud extracts the article and translates/summarizes it with AI → the device syncs over Wi-Fi and you read it page by page.
| Reading queue | Reader (AI summary first) | Almanac standby |
|---|---|---|
![]() |
![]() |
![]() |
Phone / Desktop Cloud (Cloudflare Workers) PaperS3 device
┌──────────────┐ share URL ┌────────────────────────────────┐ Wi-Fi ┌──────────────────┐
│ Share sheet │ ──POST──▶ │ 1. Extract content (crawler) │ ◀─GET── │ Instant cached │
│ iOS Shortcut │ │ 2. Async AI translate/summarize │ │ list, paged e-ink │
└──────────────┘ │ 3. D1 queue + device API + web │ ──POST─▶ │ reading, offline │
└────────────────────────────────┘ read │ cache, stats │
└──────────────────┘
| Directory | Contents |
|---|---|
server/ |
Cloud backend: TanStack Start + Cloudflare Workers + D1 + Workers AI. Article extraction, AI translation/summary, queue, device REST API, web reader, iOS Shortcut endpoint |
firmware/ |
Device firmware: PlatformIO + Arduino + M5Unified/M5GFX. List screen, paged e-ink reader, offline cache, background sync, reading stats, QR sharing, flip-to-sleep, almanac standby screen, anti-aliased CJK font |
- Add — share a page from any app → iOS Shortcut
POST /api/articles {url}→ the server records the URL and returns within seconds (status: queued); crawling and AI processing continue in the background. - Process — a background queue (
waitUntil+ per-minute cron) translates non-Chinese articles, generates a summary, and cleans up the layout. Only fully processed articles reach the device feed. - Read — the device boots straight into its cached list; a background sync (which never blocks the UI) pulls new articles and pre-caches their bodies. Open an article and read offline; the AI summary leads the text.
- Recycle — mark as read → the device frees its local cache and the item leaves the queue; the server keeps read articles for 30 days, then a cron purges them.
Cloud
- Content extraction via the search1api crawl service (handles JS-heavy pages); failures are retried up to 3 times, then surfaced as
failed. - Async Workers AI processing (Qwen3-30B): translates to Chinese, tidies the layout, prepends a summary; long articles are translated in parallel chunks. Personal-scale usage generally fits the free daily quota.
- Web reader can switch between the processed translation and the original text.
- Almanac endpoint (
lunar-typescript, fully offline lunar calendar math) powers the device's standby screen.
Device
- Offline-first: list, bodies, and reading progress cached on internal flash (LittleFS); boots instantly, reads without a network.
- Non-blocking sync: all network work runs on a background task — you can open and read articles while the device syncs.
- Flip to sleep: place the device face-down and it locks immediately (BMI270 accelerometer), no countdown.
- QR sharing: on an article's last page, tap the bottom-right corner to show an offline-generated QR code of the original URL.
- Power: Wi-Fi off while reading; deep sleep (~9 µA) when idle, with a zero-power almanac standby screen.
- Anti-aliased CJK text: a VLW font stored in its own flash partition, memory-mapped (zero RAM).
- Reading stats (today/total time + words), per-article progress memory, visible sync status via partial refresh.
Cloud (see server/README.md)
cd server && npm install
npx wrangler d1 migrations apply papers3-reader --local
npm run dev # http://localhost:3000Firmware (see firmware/README.md)
cd firmware
cp src/config.example.h src/config.h # fill in Wi-Fi, server URL, token
pio run -t upload # flash (device must be in download mode)Sending articles — add them on the web UI, or set up the one-tap iOS Shortcut so any page in your share sheet lands on the device.


