Turn a reMarkable tablet into a Tesserae dashboard panel. The app runs inside the reMarkable's own interface through AppLoad, pairs with your self-hosted Tesserae server over its v1 REST device API, fetches each rendered frame, shows it, and puts the tablet into deep sleep until the server wants it back. Refresh cadence, schedules, rotations and quiet hours are all decided on the server; the tablet just wakes, paints and sleeps.
- reMarkable 2 (tested) and reMarkable 1: 1404 x 1872, 16-level greyscale, dithered on the server with the algorithm you pick per device.
- reMarkable Paper Pro: 1620 x 2160 colour PNG through the tablet's own colour pipeline (built, not yet confirmed on hardware).
- Zero-touch pairing: the tablet announces itself, you click Register in Tesserae. A 6-digit pairing code works too.
- Deep sleep between refreshes with an RTC wake alarm. By default the tablet stays awake while charging (for up to four hours) and sleeps on battery.
- ETag-conditional polls, so unchanged content costs one small request and no e-ink flash. Battery, signal, IP and errors are reported to the device card.
- Keeps the last frame on screen when the network is down, with a bounded retry backoff, and forwards errors to Tesserae's Events page.
This is a community project. It is not affiliated with reMarkable or with the Tesserae maintainer, and a reMarkable firmware update can break it. It is derived from the MIT-licensed TRMNL for reMarkable app, which did the hard work of running a Go backend and a QML frontend inside xochitl; see THIRD_PARTY_NOTICES.md.
Caution
Enabling Developer Mode factory-resets the tablet. Sync or export everything you care about first. Developer Mode also lowers the tablet's security, and uninstalling this app does not turn it back off. Read reMarkable's Developer Mode notes before you start.
reMarkable (AppLoad app) Tesserae server
Tesserae.qml <-- AppLoad socket --> backend/entry (Go)
| POST /api/v1/device/discover (until you click Register)
| POST /api/v1/device/<id>/status -> next_poll_s, config
| GET /api/v1/device/<id>/frame -> 200 envelope | 304 | 204
| GET /renders/<render_id>.bin|png
v
paint -> arm RTC alarm -> systemctl suspend
The server renders your dashboard at the panel's exact size. For the
greyscale tablets it sends a packed 4-bit, 16-grey buffer already dithered
(esp32_gray_bin); the app unpacks it into an image and hands it to the
tablet's display stack. For the Paper Pro it sends a plain 24-bit PNG.
| Tablet | reMarkable 2 or reMarkable 1 on OS 3.20 to 3.27, or a Paper Pro on 3.26 or 3.27, with Developer Mode (SSH) enabled andXOVI + AppLoad installed |
| Server | A runningTesserae (0.392 or newer), reachable from the tablet's Wi-Fi network. Plain http:// on your LAN is fine |
| Computer | macOS, Linux or Windows with Go 1.22+ and Qt 6 (for rcc), and SSH access to the tablet |
reMarkable Paper Pro and reMarkable 2 on recent firmware (3.20+): Developer Mode
-
Sync or export your notebooks. The next step erases the tablet.
-
On the tablet open Settings → General → Software → Advanced (the exact place has moved between releases; look for Developer mode) and turn it on. Confirm the factory reset and complete the onboarding again.
-
Go to Settings → General → Help → Copyrights and licenses. At the bottom you will find the SSH password and the USB address (
10.11.99.1). -
Connect the USB cable and check you can log in from your computer:
ssh root@10.11.99.1On Wi-Fi the tablet's address is shown under Settings → Help → Copyrights and licenses too, or on your router.
reMarkable 1 and older reMarkable 2 firmware
These do not have a Developer Mode switch: SSH is always on. The password is
under Settings → Help → Copyrights and licenses → GPLv3 Compliance (scroll
down). Use it with ssh root@10.11.99.1 over USB.
Once you are in, copy your SSH key so the deploy script does not ask for the password each time:
ssh-copy-id root@10.11.99.1
(If ssh-copy-id is missing: cat ~/.ssh/id_ed25519.pub | ssh root@10.11.99.1 'mkdir -p ~/.ssh && cat >> ~/.ssh/authorized_keys'.)
The app is an AppLoad application. Follow the install instructions in the rm-appload and rm-xovi-extensions READMEs for your tablet, then confirm the AppLoad icon appears in the reMarkable sidebar and that this directory exists on the tablet:
/home/root/xovi/exthome/appload
XOVI is not persistent across reboots by design: after a reboot run
/home/root/xovi/start over SSH to bring AppLoad back.
On your computer:
brew install go qt # macOS; on Debian/Ubuntu: golang qt6-base-dev-tools qt6-declarative-dev-tools
git clone https://github.com/partridgeworks/tesserae.remarkable
cd tesserae.remarkable
make test # unit tests, vet, QML lint
make build # dist/tesserae-remarkable-app (Paper Pro) and dist/tesserae-remarkable-app-arm32 (rM1/rM2)
make deploy TABLET=root@10.11.99.1
make deploy detects the tablet's architecture, copies the matching bundle to
/home/root/xovi/exthome/appload/tesserae-remarkable, runs the bundle's
self-check on the tablet, and restarts a running backend. Settings, cache and
pairing survive a redeploy.
When redeploying a newer version, note that xochitl keeps the app's QML cached
in memory once it has been opened. A new backend is picked up on the next
launch, but a changed screen layout needs xochitl restarted: reboot the tablet
(or ssh root@10.11.99.1 systemctl restart xochitl, which on some firmware
also reboots), then run /home/root/xovi/start and open the app again.
Prefer to copy by hand? After make build:
scp -r dist/tesserae-remarkable-app-arm32 root@10.11.99.1:/home/root/xovi/exthome/appload/tesserae-remarkable
(use dist/tesserae-remarkable-app for a Paper Pro).
Optionally seed the server address so you do not have to type it on the tablet:
ssh root@10.11.99.1 'mkdir -p ~/.config/tesserae-remarkable && printf "%s\n" "{\"server_url\": \"http://192.168.1.4:8765\"}" > ~/.config/tesserae-remarkable/config.json && chmod 600 ~/.config/tesserae-remarkable/config.json'
Tesserae picks the renderer from the device kind. The reMarkable kinds are data-only catalog entries in server/hardware/remarkable/ until they ship with Tesserae. Install them on your server and restart it:
-
Docker: copy the files next to your compose file and bind-mount them, then
docker compose up -d:volumes: - ./hardware/remarkable/remarkable_2.json:/app/hardware/remarkable/remarkable_2.json:ro - ./hardware/remarkable/remarkable_1.json:/app/hardware/remarkable/remarkable_1.json:ro - ./hardware/remarkable/paper_pro.json:/app/hardware/remarkable/paper_pro.json:ro
-
Bare install / LXC: copy them to
<tesserae>/hardware/remarkable/and restart the service.
docker logs tesserae | grep "hardware-catalog kind remarkable" confirms they
loaded. Without them the app pairs as the stock esp32_client kind (which
renders a colour Spectra-6 buffer the app converts to grey) and moves itself
to the right kind automatically the first time it finds the entry installed.
- On the tablet open AppLoad and tap Tesserae.
- If you did not seed the address, tap the top-right corner, open
Settings, enter the server address (for example
http://192.168.1.2:8765), tap Test connection, then Save. - The screen shows Waiting for Register in Tesserae with the device id and MAC. In Tesserae open Settings → Devices; the tablet is in the Discovered strip. Click Register (optionally rename it).
- Within 30 seconds the tablet receives its token and shows bind a dashboard. In Tesserae, bind a dashboard to the device and press Send.
- The first frame appears. From now on the tablet follows the server's Sleep interval (device card), schedules and rotations.
Can't be at the browser while the tablet announces itself? Mint a code under Settings → Devices → Pair new device, type it into the app's Settings and tap Pair with code.
| What | Where |
|---|---|
| Menu (controls, settings, diagnostics) | Tap the upper-right corner |
| Refresh now / Fetch latest / Previous frame / Sleep now | Buttons in the menu |
| Front light and weekly brightness schedule (Paper Pro) | Menu |
| History of refreshes, sleeps and errors | Menu |
| Diagnostics (token redacted) | Menu → Diagnostics |
| Back to reMarkable | AppLoad's swipe from the top centre, the on-screen button, or hold the upper-left corner for two seconds |
Settings live in /home/root/.config/tesserae-remarkable/config.json (mode
0600, token included); cache in ~/.cache/tesserae-remarkable; logs and state
in ~/.local/share/tesserae-remarkable.
The server decides when the tablet should come back (next_poll_s, or an
absolute wake_at with synchronized wake). The app decides what the tablet
does in between:
| Power mode | Behaviour |
|---|---|
| Sleep on battery, stay awake while charging (default) | On battery the tablet arms its RTC alarm and suspends a few seconds after each refresh. On the charger it stays awake and polls, for up to 4 hours (configurable), then sleeps between refreshes anyway. |
| Always sleep between refreshes | Best battery life. |
| Never sleep | For a mains-powered wall panel. The app advertises can_stay_awake, which unlocks Tesserae's Always on / Awake poll interval options on the device card for near-instant pushes. |
Settings also offer the grace period before sleeping, a floor under the refresh interval, and a battery saver that stretches the interval below 20%. A touch on the screen or an open menu postpones the sleep. Manual sends and webhooks reach a sleeping tablet on its next wake; if that matters, shorten the sleep interval on the device card or use always-on.
Recommended tablet settings: leave reMarkable's Auto sleep on and Auto power-off off; a powered-off tablet cannot wake itself.
ssh root@10.11.99.1 rm -rf /home/root/xovi/exthome/appload/tesserae-remarkable
Add ~/.config/tesserae-remarkable ~/.cache/tesserae-remarkable ~/.local/share/tesserae-remarkable to also erase settings, cache and logs.
Delete the device in Tesserae's Settings → Devices. XOVI and AppLoad are
left alone; see their own docs for removing them.
| Problem | Try |
|---|---|
| Server not reachable | Check the address and that the tablet is on Wi-Fi. Use the IP rather than a .local name if mDNS does not resolve on the tablet. |
| plain HTTP is only allowed for servers on your local network | The address is not a private/LAN one. Use HTTPS, or turn onAllow plain HTTP to any host in Settings. |
| Stuck onWaiting for Register | Open Tesserae → Settings → Devices and click Register in the Discovered strip. The device id and MAC are on the tablet's screen. |
| bind a dashboard never goes away | Bind a dashboard to this device in Tesserae and press Send. |
| Frames look 1-bit or wrongly coloured | The catalog entry is missing on the server (step 4). Diagnostics shows the paired kind and renderer. |
| Tablet does not wake for the refresh | Diagnostics shows the wake alarm state. Make sure Auto power-off is off. After a reboot run /home/root/xovi/start and open the app again. |
| Gone after reboot | XOVI does not persist across reboots; run /home/root/xovi/start over SSH. |
Logs: /home/root/.local/share/tesserae-remarkable/tesserae.log on the tablet,
and Tesserae's Settings → Events for forwarded errors.
make test # go test, vet, gofmt, qmllint
make integration # AppLoad socket harness against the mock server (Linux only: needs SOCK_SEQPACKET)
make mock # run the mock Tesserae server on 127.0.0.1:9988
Layout:
app/ui/Tesserae.qml: the AppLoad frontendbackend/cmd/tesserae-remarkable: the device backend (pairing, polling, painting, sleep)backend/internal/tesserae: the REST client;frame: frame decoders;power: RTC alarm and suspendbackend/cmd/tesserae-mock: a small Tesserae stand-in for testsserver/hardware/remarkable: Tesserae hardware catalog entriesdevice/: on-device install, uninstall, recovery and diagnostics scriptsscripts/deploy.sh: build-and-copy over SSH
MIT. See LICENSE and THIRD_PARTY_NOTICES.md.