ExplorInk
GitHub

Heavy development · nothing here is finished

A map you can
read in the sun.

ExplorInk turns a pocket e-ink reader into an offline map display for motorcycles and trails. No backlight to wash out, no battery gone by lunch. One still picture: where you are, where the route goes, what is around you.

  • 4.3″e-ink, 480×800, 220 PPI
  • 10 h+power only when the picture changes
  • 0bytes of network needed on the trail

Built on CrossPoint Reader. Map data © OpenStreetMap contributors.

The X4 panel drawing a town: outlined buildings, hatched forest, roads of different widths
real panel buildings, forest and water, 2506 ms

01 · why e-ink

A phone is a bad navigation display on a motorbike.

The screen washes out in direct sun. It cooks in a handlebar mount. The battery does not last a day of riding. E-ink has the opposite properties, and one hard cost you have to design around.

Readable in full sun

Reflective panel, no backlight. The brighter the day, the better it looks. Visor down, glance, done.

Days, not hours

Holding an image costs nothing. Power goes only into changing the picture, so the X4 runs for tens of hours.

Slow, and grey

A full refresh takes about 1.7 s; four grey levels cost an extra waveform pass, and lose their grey to the next one. That rules out a moving map.

So: a still picture

Position, route, surroundings: redrawn rarely, readable at a glance at speed. No rerouting, no voice, no traffic. The route is planned before you leave.

02 · the screen

Real framebuffers, not mockups.

The ones tagged device came off the panel over USB: send a coordinate and the device draws what is around it from tiles on its SD card. The one tagged preview is the same renderer running on a laptop against the same tiles, which is how styling gets tuned without reflashing. We keep the two labelled apart on purpose.

Honest caveat: the device renderer follows most of the style spec now: road widths and casings, buildings, forest, built-up, filled water with waves, and railways as a blocked line all draw on the panel, and the route draws with them, and so do place names. Junction dots exist in the spec, not yet on the glass, and neither do the edge markers for a village just off the screen. The laptop preview is the other way round for the route: it renders everything here except the route line, so that one is judged on the device only.

03 · how it is built

Four parts. All four exist.

  1. 01

    Map builder laptop

    Fetches an area from OpenStreetMap, projects and clips it, routes along real roads, and writes binary .tib tiles at three zoom levels. A browser tuning tool renders a pixel-exact 480×800 preview, so the styling gets judged as e-ink instead of as a pretty vector map.

  2. 02

    Tile server tiles.explorink.com

    The built maps are on a public server, so nobody has to run the map builder to go riding: the phone downloads the squares the reader is missing, one plain file per square, and reads a small index to tell an empty square from one nobody has built — and a square that has gone out of date from a current one.

    It also builds what it does not have. A square that comes back missing is written to the server’s own log, and a builder on the host takes the ten most-asked-for squares of the last day and builds them from OpenStreetMap straight into what it serves — about twenty seconds for a 13 by 13 km area, which is the missing square and the ground around it. Nothing already published is ever rewritten, and the day is capped, so an unattended builder cannot run away with the disk.

  3. 03

    Firmware ExplorInk, on the X4

    A fork of CrossPoint Reader. Map screen, BLE position receiver, tiles off the SD card, zoom and marker height on the physical buttons, ride / hike / cycle filter, and a command console over USB serial and BLE. The e-reader stack comes out; maps go in.

  4. 04

    Phone app Android

    Sends the phone's GPS fix to the device over BLE every few seconds and records every fix and packet for replay. One window, no settings screen, foreground service: it keeps sending with the phone locked in a bag. A GPS liveness gate throws away network fixes that would jump you across town. It touches GPS only while the reader is actually connected, and shuts itself down a few minutes after the reader goes away — the reader opening its map starts it again. It is also the reader’s way to the tile server: the reader asks for a missing square, the phone fetches it and pushes it over Bluetooth onto the SD card, mid-ride. Source: github.com/rfordinal/explorink-android.

OpenStreetMap map builder laptop, or the server itself tile server tiles.explorink.com phone over BLE SD card .tib tiles X4 panel phone GPS over BLE

04 · status

What works today.

Formats, the render spec and the BLE protocol all still change without notice. Do not depend on any of it yet.

  • USB serial access, flash backup and inspectionworks
  • Map builder: OSM fetch, projection, clipping, routing on real roadsworks
  • Map builder: browser tuning tool with device-exact previewworks
  • Binary .tib tiles, three zoom levelsworks
  • Firmware: map screen and BLE position receiverworks
  • Firmware: tiles loaded off the SD cardworks
  • Firmware: command console over USB serial and BLEworks
  • Firmware: zoom and marker height on the buttons, seven zoom rungs from 1 to 45 m/pxworks
  • Firmware: observation mode: pan the map on the buttons instead of following the fixworks
  • Firmware: north-up or frozen-heading map, switchable from Settings or the map menuworks
  • Firmware: ride / hike / cycle filterworks
  • Firmware: four grey levels, verified on the panel (map uses dither instead)works
  • Firmware: grey screenshots over USB serial, decoded on the laptopworks
  • Phone app: BLE position sender and ride recorderworks
  • Firmware: road widths and casings, buildings, forest, built-up and water areasworks
  • Rivers and lakes drawn as filled water with waves, not as a lineworks
  • Railways drawn the way a map draws them: a cased line in alternating blocksworks
  • Watercourses broken, so a stream cannot be mistaken for a roadworks
  • Zoomed-out views drop what you cannot use there: field tracks, ditches, rail sidingsworks
  • Firmware: map files pushed onto the SD card over BLEworks
  • Firmware: the marker follows the fix; the map only redraws when it has toworks
  • Firmware: track-up map, north indicator turns with itworks
  • Fetch missing tiles: the device asks, the phone brings them from the tile serverworks
  • Public tile server, live: maps served square by square, so nobody has to build oneworks
  • Map builder: build a route from waypoints, a GPX file, a recorded ride or the browser toolworks
  • Firmware: pick a route when the map opens, and see all of it at onceworks
  • Firmware: with a route loaded the map holds still, only the marker movesworks
  • Fill a gap without stopping: the map asks for a missing square the moment it hits oneworks
  • Firmware: town and village names on the map, outlined in white, kept off the routeworks
  • Firmware: GPS/tile/BLE debug readout, off by default, in Settings and the map menuworks
  • Out-of-date squares: the device asks the phone whether the map it holds is still current, off by defaultworks
  • Open the map and the phone starts sending on its own, app closed, nothing tappedworks
  • The phone pairs with one reader, so someone else’s does not get your positionworks
  • Firmware: a classic scale bar on the map, ticks and rounded distancesworks
  • Firmware: fixed header strip with a separator, and the nearest named place shown top-leftworks
  • The tile server builds what riders ask for, unattended: a square nobody has built yet is on the server a minute laterworks
  • A published square is never rewritten, and the builder’s day is capped, so an unwatched job cannot run awayworks
  • Renderer: place labels and junction dotsplanned
  • Route following and off-route warning on the deviceplanned
  • Anything you could call a releaseplanned

05 · development log

Recent work, newest first.

  1. The map says where you are, in words

    Until today the map drew a dot for every town and village and never a name. The dot tells you something is there; it does not tell you it is Modra. Names are on the glass now, in two sizes — towns bold, villages lighter — and at the widest zoom the reader fits a dozen of them across 24 by 40 kilometres.

    The hard part on a screen with no colour is keeping a name readable over a road network without blanking out the map to do it. Each name is outlined in white, letter by letter, so the roads and the forest keep running underneath. And a name is dropped rather than laid across your route, over another name, or under the buttons — the dot stays either way, so nothing goes missing, it just goes unnamed. Twelve names cost 163 milliseconds of a redraw that takes nearly four seconds.

  2. The phone app stops working when there is nothing to work for

    Turn the reader off and the phone app used to carry on: GPS at one fix a second, the processor held awake, and a notification saying it was still sending your position. It was not sending anything — there was nothing on the other end. Swipe the notification away and it came back within a second.

    All of it now hangs off one question: is the reader connected, or are you recording a ride? If neither, the app asks for no position, holds nothing awake, and after five minutes it shuts itself down. Opening the map on the reader starts it again on its own, so there is nothing to remember and nothing to press. The notification tells the truth in the meantime, and once dismissed it stays dismissed.

  3. Closing a menu should not cost you the map

    The map's menu — the one behind the middle button — used to throw the map away when you closed it. Every dismissal read the map squares off the card again and repainted the whole screen, seconds of it, to arrive back at the picture that was already there. The reader now keeps the pixels the menu covers and puts them back, refreshing only that patch of glass. Nothing to wait for.

    The menu itself reads like the settings screen now: every line starts at the same edge, and the part you can change — ride mode, north up or heading up, and the rest — sits on the right in a filled box, so you can see what a line is set to without opening it. And the middle button no longer says “Select” on the map. It opens a menu, so it says “Options”.

    Keeping those pixels costs memory, and the reader has 54 kilobytes free with a map on screen — measured, not guessed. The menu was eating twenty of them, because the box grew a line taller with every option in it. It has a ceiling now: six lines on screen, the rest scrolls, and the box stops growing no matter how long the list gets. Nine kilobytes instead of twenty, and the map behind it stays visible.

  4. Two more steps out, for the day you ride a whole range

    The zoom buttons stopped at 20 metres per pixel, which is about ten kilometres of ground down the screen. Enough to see the next village, not enough to see where the hills you are riding actually go. There are two more steps now, and the widest shows 24 by 40 kilometres — the full width of the Malé Karpaty, both flanks, every road that crosses the ridge, on one screen.

    Zooming out that far is not free: the reader pulls twelve map squares off the card instead of six and draws them in 3.6 seconds. That is half of what the closest zoom already costs, and the map is only redrawn when you have moved far enough to need it.

    Two things had to stop being fixed sizes for this to feel right. The position marker: it is a fixed dot on the glass, so the further out you go the more ground it hides — two kilometres of it, at the widest step. It is drawn smaller up there now. And the distance you have to travel before the marker is redrawn, which was a fixed number of pixels: eight metres of ground at the closest step, but three hundred and sixty at the widest, so the view that needed a steady trickle of updates got almost none. It follows the zoom now too.

  5. Ride off the edge of the map and the map comes to you

    Until today, a square nobody had built yet stayed a hatched square. The reader asks the phone for it, the phone asks the tile server, the server does not have it — and that was the end of the story.

    Now that miss is the request. The tile server watches its own log of squares it could not serve, takes the ten most asked-for from the last day, and builds them from OpenStreetMap straight into what it serves. One build at a time, and the area around the square goes with it, so the ground you are riding into is there too. Measured on the server: about twenty seconds per area.

    The reader had to learn patience for that to be worth anything. It used to cross a square off for good once the phone said it could not get it — sensible when a miss meant nobody had it, wrong now that the miss is what builds it. It waits and asks again instead, backing off from a minute and a half to an hour so a square that truly does not exist stops costing anyone data.

    Tested end to end on the reader, on a drive across ground the map did not cover: four squares missing, two arrived from the server, the other two were built from the reader’s own asking and drew a minute later, while it kept moving. Nothing on the server rewrites a square that already exists — a hole gets filled, everything else is left alone.

  6. A scale bar, and a header that says where you are

    Two small things the map screen was missing. A classic scale bar, bottom-left: five alternating black/white segments, ticks, and a rounded distance — 100 m, 1 km, whatever the current zoom rounds to. Numbers that would run into each other at a loose zoom are dropped, not smeared; the endpoints always stay.

    The header is now a fixed white strip with a real 1px separator below it — the map no longer draws underneath and gets covered, it stops at the line. Top-left, it now names the nearest place: read straight off the same tile data that already draws the place dots, no extra card read for it.

  7. Open the map and the phone starts sending, on its own

    Until today a ride started with a chore: unlock the phone, find the app, open it, wait for it to find the reader. Now the reader does it. Open the map on the device and the phone starts sending its position by itself — app closed, screen locked, nothing tapped. The signal was already there: the reader only talks over Bluetooth while the map is on screen, so the map opening is the announcement. What was missing was a phone that could hear it while shut down.

    The phone now pairs with one reader, once, in a system dialog. That fixed a second problem nobody had noticed: two of these readers look identical over the air, same name, same service. The app used to talk to whichever answered first, so a second device in the room was a coin toss — and the loser gets a stranger’s position on their map. It is now a fixed address, not a guess.

    Verified on the real device with a real phone: app killed, map opened, the phone woke itself, connected and sent a position with nobody touching it. Then again after restarting the phone, which is the case that usually breaks this kind of thing.

    Real hardware found a real bug in five minutes, again. The pairing screen sat empty while the app was connected to the very device it was looking for: the reader stops announcing itself once a phone connects, and the pairing screen can only offer what it can hear. So the app now steps off the link while you pair, and steps back on afterwards.

The full dated log lives in the repo, alongside one topic document per hardware finding: how the panel actually behaves, why the obvious approach fails. Read it on GitHub →

06 · the hardware

Xteink X4

A pocket e-ink reader, bought off the shelf. Not a dev board, not Android, no ADB: an ESP32-C3 with a display driver and physical buttons. Everything ExplorInk does has to fit in that.

Panel
4.3″ e-ink, 480×800, 220 PPI, no touch
Grey
4 levels, ~1.7 s full refresh, ~0.5 s fast; map fills use 2×2 dither
SoC
ESP32-C3, WiFi + Bluetooth 5 LE
Storage
16 MB internal flash, maps on a separate SD card
Input
Physical buttons only
Wireless
BLE preferred over WiFi for everything
Xteink X4 clipped to a car dashboard mount next to the car's own navigation screen, showing a real schematic map with a position marker, zoom controls and Exit/Select/Up/Down button labels
Real test drive, real panel: coordinates, ride time, tiles read and draw time in the debug line up top.

07 · early access

Nothing is installable yet. Get told when it is.

ExplorInk runs on real hardware today, but installing it still means a cable and a flashing tool. The maps are no longer the hard part — they are on a server that builds what riders ask for, and the phone brings them to the reader by itself. One-click install and a downloadable app are what comes next. Leave an address and you hear from me once, when there is something to put on a device.

Do you already own an Xteink X4 or X4 Pro?

One address, held by Comsultia, s.r.o. and used only to tell you when ExplorInk is installable. No list, no sharing, no newsletter.

08 · get in touch

Built at Comsultia, in the open.

ExplorInk is developed by Comsultia, s.r.o., a software company in Slovakia. It started because I ride, and I wanted a display that survives a sunny day on a handlebar. If you are doing something similar on Xteink hardware, or you want to try it, or you just want to tell me the map style is wrong, write to us.

Email

roman.fordinal@comsultia.com

Company

comsultia.com

Comsultia, s.r.o. — the company behind ExplorInk.

Code

github.com/rfordinal/explorink

Firmware, docs and every hardware finding. MIT licensed.