Web-based firmware flasher, build system, and font builder for CrossPoint Reader devices. Hosted at crosspointreader.com.
- Stable firmware flashing — Flash the latest CrossPoint release or stock Xteink firmware to X3 and X4 devices directly from the browser using WebSerial
- Insider builds — Nightly firmware compiled automatically from the upstream
masterbranch - Beta builds — Curated test builds exposed alongside stable, sourced from either an admin-uploaded
.binor a tagged GitHub release on the firmware repo - Custom font firmware — Replace built-in fonts in the firmware with user-supplied TTF/OTF files via a CI build
- SD-card font builder — Convert TTF/OTF files into
.cpfontfiles for SD-card font loading on the device, using the firmware repo's own conversion script (no client-side reimplementation) - Stock firmware — Restore original Xteink firmware (English or Chinese) for both X3 and X4
- Admin dashboard — Manually trigger builds, manage beta entries, and monitor build status
The project runs on Cloudflare Workers with GitHub Actions handling everything that needs a real toolchain (firmware compilation, font conversion):
- Worker (
src/index.ts) — API routes, firmware proxying, beta/font orchestration, static asset serving - R2 (
crosspoint-firmware) — Compiled firmware binaries and font build artifacts - KV (
BUILD_META) — Build metadata, sha caches, font build state - Static assets (
public/) — HTML pages, the WebSerial flasher module, the admin dashboard, and bundled X3 firmware - GitHub Actions workflows in
.github/workflows/— long-running build jobs the Worker can dispatch
A daily cron job (or manual trigger from /admin) dispatches the workflow, which:
- Checks the upstream crosspoint-reader/crosspoint-reader
masterfor new commits - If there's a new commit (or the previous run failed), clones with submodules, installs the pioarduino PlatformIO fork, and runs
pio run -e gh_release - Uploads the resulting
firmware.binto R2 via the Worker API - Stores build metadata (version, commit, changelog since last tag) in KV
Insider builds are exposed at /insider and surfaced in the catalog under the insider channel.
Lets a user replace one or more built-in fonts in the firmware:
- The user uploads TTFs via
/fonts(the "Custom Font Firmware" form) - Worker stores them in R2 under
builds/custom/{buildId}/fonts/...and dispatches the workflow - Workflow checks out the firmware repo, drops the user's TTFs over the built-in font sources, applies any label/size overrides, runs the firmware-side font conversion, then builds the firmware
- The completed
firmware.binis uploaded back to the Worker; the user is offered a download
Converts arbitrary TTF/OTF files into .cpfont files for SD-card loading without recompiling firmware:
- The user uploads up to four styles (regular, bold, italic, bold-italic) plus a family name, point sizes, and a Unicode interval preset at
/fonts - Worker stores the TTFs in R2 under
font-builds/{buildId}/in/and dispatches the workflow - Workflow checks out
crosspoint-reader/crosspoint-reader(currently pinned to thefeature/sd-fonts-v1branch via thereaderRefinput), installsfreetype-py fonttools brotli, and runslib/EpdFont/scripts/fontconvert_sdcard.pyfrom the firmware repo unmodified — same script the device-side build uses, so output is byte-identical to a host run - Each
.cpfontis uploaded back to the Worker underfont-builds/{buildId}/out/ - The frontend polls
/api/font-build/status, streams the script's stderr (glyph counts, kerning pair counts) into the UI, and offers individual or zipped downloads
The script is deliberately not vendored into this repo — the firmware repo owns the conversion logic. To bump the script version, change the readerRef workflow input (or the dispatch payload in handleFontBuildUpload) to a different ref/tag.
Built .cpfont files install on the device by copying to the SD card under /fonts/YourFont/ (or /.fonts/YourFont/ for a hidden folder).
Beta builds appear in the catalog as a separate channel and are managed from the admin dashboard. Each entry has one of two sources:
- Upload — Admin attaches a local
.bin. Stored verbatim in R2. - GitHub release — Admin enters a release tag (default repo
crosspoint-reader/crosspoint-reader). The Worker resolves the release via the GitHub API, fetches itsfirmware.binasset (or the first.binasset iffirmware.binisn't present), and caches the bytes into R2 under the same key as an upload. The original release tag is recorded so the entry can be re-fetched later.
Editing an entry lets you re-link it to a different release tag, which transparently replaces the stored binary. Existing upload-backed betas keep working — the source field is optional and absent legacy entries are treated as uploads.
The browser-based flasher (public/js/flasher.js) uses esptool-js via WebSerial:
- Connects to the ESP32-C3 over USB serial
- Reads and validates the partition table (X3 or X4)
- Writes firmware to the backup OTA partition
- Updates the OTA boot selector to swap partitions on next boot
- Node.js 20+
- A Cloudflare Workers Paid plan
- A GitHub classic personal access token with
workflowscope (for dispatching workflows in this repo)
npm install
# Create Cloudflare resources (first time only)
npx wrangler r2 bucket create crosspoint-firmware
npx wrangler kv namespace create BUILD_META
# Update the KV namespace ID in wrangler.jsonc
# Set secrets
npx wrangler secret put GITHUB_WEBHOOK_SECRET # Generate with: openssl rand -hex 32
npx wrangler secret put GITHUB_TOKEN # Classic PAT with workflow scope
# Deploy
npm run deploySet these on the SoFriendly/crosspoint-tools repo (Settings → Secrets → Actions):
WEBHOOK_SECRET— Same value asGITHUB_WEBHOOK_SECRETin the Worker. Used by every workflow to authenticate callbacks (status updates, asset downloads, result uploads).GH_PAT— GitHub classic PAT (no scopes needed if everything you read is public). Used by the workflows for higher API rate limits when reading the upstream firmware repo.
npm run dev # Starts wrangler dev server on localhost:8787The WebSerial flasher is based on xteink-flasher, licensed under the MIT License. The partition table handling, OTA flashing logic, and device model support were adapted from that project.
The SD-card font builder runs fontconvert_sdcard.py from the crosspoint-reader firmware repo verbatim, so any kerning/ligature behavior is whatever that script implements — this site is just a thin frontend.
MIT