ExplorInk
GitHub

development · trips ·

A trip is a line on the map, not a set of instructions

Plan a route on the phone with a handful of stops, share it, pick ExplorInk, and the trip is on the reader's card and drawn across the map. From then on the reader does what it always does: it shows where you are and what is around you, with your line running through it. No turn instructions, no recalculating, no voice, and no warning that you have left it.

This ships today on every supported device, in the published builds. The phone side is ExplorInk GPS 0.3.6, verified end to end on 2026-10-27: a six-stop route from a Samsung S24 Ultra, 346 points and 2.8 kB, on the card and ridden from the picker.

Why there are no turn instructions, and never will be

A reader that tells you to turn left is answering am I going the right way. That is the question this product does not answer, by design: it answers where you are and what is around you, the way a paper map does. An off-route warning was on our own roadmap for weeks and was dropped on 2026-10-27 for exactly that reason, by the person who had put it there.

What is left is the honest half, and it is the half a paper map already proves people can use: the planned line, drawn across the ground you are standing on. You compare the two yourself, which is a thing humans are good at and a thing that needs no battery, no voice and no attention on a screen.

Sharing a route from Google Maps to the reader

On the phone: plan the route in Google Maps, hit Share, pick ExplorInk GPS. The app shows every stop it read, with coordinates, and asks before it sends anything. Then the trip goes over Bluetooth to the reader's card, and it is in the picker the next time you open Trips.

If the reader is not switched on, the trip waits in a queue on the phone and goes across the next time the two are connected. A trip is small: a six-stop route came out at 346 points and 2.8 kB, and even a 3,000-point route is 24 kB, so the transfer is seconds rather than minutes.

The line is ours, not Google's, and the stop list is the only check

Google Maps does not share the route. A share hands over a short link, and resolving it gives the stops, as coordinates or as place names, and no geometry at all. There is no polyline in it at any point.

So the app recovers the stops and routes them itself, along real roads, with a routing engine of its own. The line the reader draws is that engine's line. It will differ from the one you saw on the Google screen, sometimes by a street and sometimes by a valley, and the app says so on the screen rather than hiding it.

That is also why the stop list matters. It is shown before anything is sent, and it is the only check that exists: a stop read wrong bends the whole trip, and nothing after that point can notice, because the file that comes out is valid and its checksums match. A human reading five lines of coordinates is the error check, and it is deliberate.

What a trip file is: one .tir per trip on the card

Trips live in /explorink/trips/, flat, one file per trip, written by the phone or by a laptop tool. The format is ours and it is small on purpose, because the reader is a microcontroller reading an SD card:

PartWhat is in it
Header, 40 bytesMagic, version, point count, bounding box, build time, and two CRC32 checksums, one for the header and one for the points
NameUp to 64 bytes of UTF-8, inside the file. Renaming the file cannot rename the trip
PointsTwo 32-bit integers each, absolute Web Mercator metres, rounded to whole metres

Whole metres are enough because one Mercator metre is a fifth of a pixel at the finest zoom the reader draws. Absolute coordinates rather than tile-local ones because a trip is not clipped to the tile grid: it is one file, read once when the map opens, not a thing streamed per tile. Both ends round-trip the same bytes under test, the Python writer on the laptop and the C++ reader on the device.

The picker on the reader: load a trip, or skip to the map

Trips is its own row on the home screen. It lists what is on the card with the point count of each, plus a Continue to map row for going out with no plan, which is the normal case and therefore the first row rather than a corner exit.

Real X4 Pro framebuffer: a trip picker headed Load a trip, listing three trips with their point counts above a highlighted row, and a first row offering to continue to the map with no trip
device The picker on an X4 Pro, 480×800, 1:1, grabbed on 2026-11-02. Three trips on the card, point counts under the names, and the hint that holding Select deletes one.

Holding Select on a row offers to delete that trip, and deleting the last one leaves to an empty screen that says nothing has been sent from the phone yet. That arrived on 2026-10-27 with the share feature, because until then a card collected every trip ever sent to it and nothing on the device could remove one.

Real X4 Pro framebuffer: an empty trips screen saying no trips on the card, nothing sent from the phone yet, over a line drawing of mountains
device A card with no trips on it, X4 Pro, 480×800, 1:1. The row that goes to the map is still there, because going out with no plan is the normal case.

A file the reader cannot read says so in the list, on its own row, rather than disappearing or taking the screen down with it.

The first frame is the whole trip, turned the way it runs

Picking a trip draws an overview: the zoom step and the orientation that show the most of it. The fit measures the points, not the bounding box, because a trip running north-east fills a thin diagonal strip inside a very large box, and the panel is taller than it is wide, so which way the map is turned changes what fits. Confirmed on the panel on 2026-08-06: a 71-point trip running north-east came out at the third zoom step of the ladder, rotated to match, whole trip on screen, with the compass turned with it.

Real X4 framebuffer showing a planned trip as a thick black line with an arrowhead at its far end, over roads and a river, with diagonal hatching down the left edge
device The opening frame of a trip, X4, 480×800, 1:1, built in 2,537 ms. The arrowhead marks the far end. The hatching on the left is honest: no map data for that square.

Among orientations that land on the same zoom step the fit picks the one nearest the direction of travel, and a closed loop falls back to north up, which is the one orientation anybody can read. No marker is drawn on this frame: a dot in the middle of your own trip would claim you are standing there. And a position arriving while the overview is up is recorded but does not redraw it, because the phone sends one every five seconds and the overview would be snatched away before it could be read.

A trip longer than the coarsest zoom step can show gets the middle of it, and the frame says so out loud, because a frame showing part of a long trip looks exactly like one showing all of a short one.

Real X4 Pro framebuffer: a town map near Modra with a planned trip drawn as a thick black line running across it and the position marker sitting on that line
device What it looks like on the way, X4 Pro, 480×800, 1:1, grabbed on 2026-11-02. The trip is the thick line, the dashed ring is where you are, and everything else is the ordinary map.

Walking or riding it: there is no trip mode

After the overview, the reader is in the same follow loop it uses with no trip at all. The code that handles an incoming position never looks at the trip: it is one more layer drawn on top. That is the whole design, and it is why nothing on the device can nag you about leaving the line. It does not know you have.

One thing does change, and it is about the picture rather than the route. Normally the map is rebuilt when your heading drifts far enough, which on a mountain pass means a new frame every few bends, each one turned a different way. With a trip loaded, the trip holds the frame: the heading check is switched off and any rebuild that does happen hands back the same orientation.

Measured on an X4 on 2026-08-07, three runs of the same 136 positions along a 3.4 km serpentine leg at 25 m spacing, same firmware:

RunFull redrawsOf those, caused by headingDistinct orientations
Trip loaded, at the zoom the fit chose001
Trip loaded, forced to the finest zoom601
No trip, the control1139

The trip turned through most of the compass in every run, so the rider really was turning. The middle row is the one worth reading twice: forced to a zoom the leg does not fit, the marker runs out of the frame six times and gets six new frames, and every one of them is the same way up. The rule is not "never redraw". It is never redraw because the rider turned, and never hand back a differently oriented map. Memory use across a whole run: unchanged, to the byte.

What is not there yet

No GPX or KML import. A GPX file carries real geometry, so importing one would put the exact line you planned on the card with no routing engine in the middle and no difference to explain. It is specified and nobody has written it. Today a file like that goes through a laptop tool instead of the phone.

The routing engine is somebody else's. The app routes against a public BRouter instance. Our own is owed, and until it exists a shared trip needs the phone to have a connection at the moment of sharing.

Google's link shape is undocumented and can change. We read stops out of a URL that Google never promised to keep stable. When it changes, sharing breaks until we follow it, and a GPX path would not have that problem, which is one more reason it is next.