Skip to content
 
 

Repository files navigation

TrailInk — tools for the trail. Ready when you are.

TrailInk

An outdoor trip companion for e-ink readers: offline maps and navigation for motorcycle rides and hiking. A fork of CrossPoint Reader, which is where nearly everything that makes this device work came from — see Credits.

Heavy development. Not usable yet. The map screen draws real OSM data off the SD card, zoom and marker height respond to the hardware buttons, and a phone can drive it all over BLE. The renderer now follows most of the map style on the real panel: per-class road widths and casings, hatched or dithered buildings, forest, built-up and water areas. No place names and no route yet — those are the next two pieces. Formats and the BLE protocol change without notice. If you want a working device today, use CrossPoint — this fork trades its features away for a different purpose.

Real 480x800 framebuffer off the device: Malacky at 3 metres per pixel, individual buildings with outlines and a dithered interior, forest as a diagonal hatch, the position marker and a compass The same place at 6 metres per pixel: the village as one stippled built-up area, forest hatch, no individual buildings

The real panel, not a render. Both frames are the device's own framebuffer, pulled over USB serial with CMD:SCREENSHOT. Malacky at 3 m/px on the left and 6 m/px on the right, off SD-card tiles. Areas are dithered or hatched, never filled solid: a solid black building swallows the roads around it on a one-bit panel. Buildings are drawn individually up close and replaced by a built-up area further out — that switch happens when the tiles are built, so the device never reads what it would not draw. The debug line and the button hints are development furniture. Viewport reset measured on the device: 2506 ms at 3 m/px reading 484 KB, 1088 ms at 6 m/px reading 198 KB.

480x800 one-bit map: a thick route through Kostolište with junction dots, a place label and the position puck 480x800 one-bit framebuffer dumped from the device: the road network around Malacky at 3 metres per pixel, every road the same width, a debug readout in the top left corner

Left: where it is going. The map tooling's own sketch of the render spec — place labels, a thick route with junction dots and the position puck, none of which the firmware draws yet. Right: the same device eight days earlier, before any of the style reached the renderer: every road one width, no areas at all. Kept as the before picture.

Why a separate fork

A phone is a bad navigation display on a motorbike. The screen washes out in direct sun, it overheats in a handlebar mount, and the battery does not last a day of riding. E-ink has the opposite properties: readable in full sunlight, free to hold an image on screen, and tens of hours of runtime because power is only spent when the picture changes.

What it gives up is refresh rate and colour. Grey it does have -- four levels, one extra waveform pass each (docs/eink-grayscale.md) -- but the refresh rate is the binding constraint. That rules out the moving map a phone gives you and forces a different design: a still image showing where you are, where the route goes and what is around you, redrawn rarely and readable at a glance at speed. No rerouting, no voice, no live traffic — the route is planned before you leave.

That is a different product from an e-reader rather than an extra feature on one, and it falls outside CrossPoint's scope, so it is a fork and not a pull request.

Status

Map activity and screen exists
BLE position receiver (phone sends GPS) exists
Loading a real map from the SD card exists
Buttons on the map screen (zoom, marker height) exists, per mode, saved across power cycles
Command console (serial and BLE), zoom/mode filter exists — same grammar over USB and BLE
Renderer following the map style spec mostly — per-class road widths and casings, hidden classes, buildings, forest, built-up and water areas with dither tones or hatch, place dots, marker anchor. Confirmed on the panel, not only in the preview. Labels, route and junction dots: not implemented
Four-level grey on the panel exists, and the map deliberately does not use it — a dither pattern read better for area fills and survives a refresh. See docs/eink-grayscale.md
Screenshots over USB serial exists — 1-bit framebuffer, plus a grey variant that re-renders both bit planes
Route following, off-route warning not started
Companion phone app exists — an Android BLE position sender and ride recorder, in the same unpublished workspace as the map tooling

data/mapstyle.json holds the styling the renderer is meant to follow — road widths and casings per class, building, forest, built-up and water area fills, junction dots, place labels, the position marker. It is written by the mapbuilder webapp rather than edited by hand. It is a build-time input, not a runtime one: scripts/gen_mode_masks.py compiles its modes block into the ride/hike/cycle class masks and scripts/gen_mapstyle.py compiles the drawing numbers into a MapStyle constant, both as pre: build steps; the device reads no style file at runtime. See docs/map-style.md.

The same renderer builds as a host binary — pio run -t map-preview renders real tiles to a PPM with no ESP32 toolchain and no flashing, which is how a style change gets checked in seconds.

The map tooling itself — OpenStreetMap fetch, projection, routing along real roads, and a browser tuner with a pixel-exact 480x800 preview — lives in a companion workspace that is not published, together with the render specification this firmware is meant to implement.

Inherited and new

Everything that makes an ESP32-C3 e-ink device work is CrossPoint's: the display driver, the partial refresh path, the activity system, settings, i18n, the web server and file manager, the build and test setup. This fork adds a map activity, a BLE position server and the map style file, and over time strips out the e-reader stack — EPUB, OPDS, dictionary, the font library — that a navigation device does not need.

Upstream fixes therefore still matter here. The intent is to keep tracking CrossPoint rather than drift away from it.

Hardware

ESP32-C3 based Xteink X4 and X3 — the same devices CrossPoint supports. Development and testing happen on an X4.

Development quick start

Prerequisites

  • pioarduino or VS Code + pioarduino plugin
  • Python 3.8+
  • clang-format 21
  • USB-C cable supporting data transfer

Setup

git clone --recursive https://github.com/rfordinal/TrailInk
cd TrailInk

# if cloned without --recursive:
git submodule update --init --recursive

Nix/NixOS

Enter the development shell with either nix develop (flakes) or nix-shell:

nix develop -f nix
# or
nix-shell nix

To flash a connected ESP32-C3 device, enable PlatformIO's udev rules in your NixOS configuration:

services.udev.packages = with pkgs; [ platformio-core.udev ];

After rebuilding the system configuration, reconnect the device or reload udev rules.

Build / flash / monitor

pio run --target upload
pio device monitor

Pre-PR checks

./bin/clang-format-fix
pio check -e default
pio run -e default

Native map preview

The map renderer and tile reader have no hardware dependency, so they build and run on a laptop without the ESP toolchain. map_preview reads real .tib tiles (mapbuilder/build_tiles.py output) around a coordinate:

cmake -S test -B build/test -DCMAKE_BUILD_TYPE=Release
cmake --build build/test --target map_preview
./build/test/map_preview/map_preview \
  --tiles test/map_tile_reader/fixtures/tiny-sd \
  --lat 48.531158410819025 --lon 17.072751469276742 \
  --heading 0 --zoom 0 --out out.ppm

It writes a 480x800 one-bit image. Note that PpmCanvas rasterizes separately from the real GfxRenderer, so the result is layout-accurate, not pixel-accurate against the device.

Documentation

The docs under docs/ are inherited and still accurate for the parts this fork has not touched — firmware internals, the activity manager, file formats, i18n and the contributing guide all came from CrossPoint.

Credits

This project exists because of CrossPoint Reader — open-source e-reader firmware for Xteink devices, community-built and fully hackable, MIT licensed, © 2025 Dave Allie and contributors. The e-ink driver, refresh handling, UI framework and toolchain in this repository are their work, not ours. TrailInk replaces only what it must for a different purpose.

If CrossPoint is useful to you, support the people who maintain it: fund contributors, or buy an X3/X4 Developer Edition through crosspointreader.com, which sends them a share of each sale.

Built on freeink-sdk by FreeInk, MIT licensed, © 2026 FreeInk.

CrossPoint in turn credits diy-esp32-epub-reader as its inspiration.

Map data © OpenStreetMap contributors, ODbL.

Licence

MIT, inherited from CrossPoint — see LICENSE.


TrailInk is not affiliated with Xteink, CrossPoint, or any device manufacturer.

About

Offline maps and navigation on e-ink for motorcycle and hiking trips. A fork of CrossPoint Reader for Xteink X3/X4.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages