Portare riddle sul reMarkable 2: guida al porting da Paper Pro ad ARMv7

Immagine di copertina

riddle è un’applicazione che trasforma un tablet reMarkable in un diario magico: scrivi con la penna, il testo svanisce nell’inchiostro elettronico, e una risposta in bella calligrafia appare sulla pagina - generata da un LLM che ha letto la tua scrittura. Il progetto nasce per il reMarkable Paper Pro, ma con un porting mirato funziona anche sul reMarkable 2.

Il codice del fork è disponibile su GitHub: serenasensini/riddle-rm2.

In questo articolo vediamo cosa abbiamo dovuto cambiare, perché, e come.


Riddle: come funziona

Analizzando la codebase, il flusso è questo:

  • l’utente scrive a penna sul tablet
  • dopo circa 2.8 secondi di inattività, il tratto viene “committato”
  • l’inchiostro viene rasterizzato in PNG
  • quel PNG viene inviato a un oracle, cioè a un backend LLM (ad esempio OpenAI)
  • il modello genera una risposta
  • la risposta arriva in streaming, a pezzi
  • riddle converte il testo in una grafia simulata e lo ridisegna sullo schermo tratto per tratto

Importante è il dettaglio circa l’utilizzo di immagini piuttosto che di OCR: riddle non legge la scrittura dell’utente, ma la trasforma in un PNG che viene inviato al modello. Il modello non “legge” il testo, ma genera una risposta basata sul contesto del PNG ricevuto.

In pratica, quando smetti di scrivere, in riddle/src/main.rs il programma aspetta IDLE_COMMIT = 2800 ms e inizia il processo di rasterizzazione, ossia la conversione dell’inchiostro in un’immagine bitmap che ritaglia la bounding box della scrittura, aggiunge un margine, riduce la dimensione per tenere il lato lungo ≤ 800 px e salva in grayscale PNG. Quindi non c’è un parser del tratto che ricostruisce lettere localmente: il riconoscimento della scrittura è delegato al modello.

A quel punto, il PNG viene letto da disco, codificato in base64 e inviato al backend LLM. La risposta arriva in streaming, e riddle la converte in tratti di calligrafia simulata, ridisegnandoli sullo schermo. Il payload della richiesta inviata all’API contiene:

  • un messaggio system con la persona di Tom Riddle
  • un messaggio user con:
  • testo: “Reply to what is written in the diary.”
  • immagine PNG della pagina.

Nota: nel caso del progetto forkato, il personaggio è stato reso leggermente più caratteristico, con un tono più sarcastico e ironico, per rendere l’esperienza più divertente. Questo è stato fatto modificando la costante PERSONA in riddle/src/oracle.rs, aggiundendo dei dettagli sul personaggio: aristocratico, freddo, con un tono leggermente minaccioso dietro a un velo di ironica gentilezza.

Una volta ricevuto un chunk di testo:

  • viene fatto il layout, ossia calcolata la posizione di ogni lettera
  • il font DancingScript.ttf viene rasterizzato
  • l’immagine viene assottigliata e vengono estratti dei tratti
  • quei tratti vengono animati come se fossero scritti a mano.

Ora che abbiamo chiara l’architettura, parliamo del porting da Paper Pro a reMarkable 2.


Il punto di partenza: due tablet molto diversi

A prima vista i due dispositivi sembrano simili - stessa filosofia, stesso OS basato su Linux. Sotto il cofano, però, differiscono su quasi tutto ciò che conta per un’app che disegna direttamente sull’e-ink:

reMarkable Paper ProreMarkable 2
CPU / architetturaaarch64 - 64 bitARMv7 (i.MX7) - 32 bit
Risoluzione schermo1620 × 21601404 × 1872
Digitalizzatore pennaElan marker inputWacom I2C Digitizer
Range asse X (penna)11 18020 966
Range asse Y (penna)15 34015 725
Pressione massima4 0964 095
Input toccotouchpt_mt (multitouch)
Dimensione evento evdev24 byte (64 bit)16 byte (32 bit)

Queste differenze, in apparenza numeriche, si traducono in comportamenti completamente sbagliati se non gestite bene: l’inchiostro finisce dall’altra parte dello schermo, specchiato, con tratti spezzati.

Il principio guida: la feature Cargo rm2

Prima di entrare nei dettagli, la scelta architetturale fondamentale: nessuna modifica spezza la build originale per il Paper Pro. Tutto il codice specifico per il reMarkable 2 è isolato dalla feature Cargo rm2, dichiarata in riddle/Cargo.toml:

[features]
takeover = []          # modalità full-takeover (esiste già)
rm2 = []               # NUOVO: tutte le differenze del reMarkable 2

Per chi non avesse familiarità con Rust, una feature è un interruttore di compilazione: il codice marcato #[cfg(feature = "rm2")] esiste solo quando esplicitamente richiesto. Senza di essa il binario è bit-per-bit identico all’originale.

Questo vuol dire che il porting non rompe nulla: chi vuole continuare a usare riddle sul Paper Pro può farlo senza problemi, mentre chi vuole il supporto al reMarkable 2 deve compilare con --features rm2.


Modifica 1 - dimensioni dello schermo (fb.rs)

Il codice che gestisce il framebuffer (il buffer di pixel scritto sull’e-ink) usava costanti fisse per la risoluzione. Sono diventate condizionali:

#[cfg(feature = "rm2")]
const SCREEN_W: u32 = 1404;
#[cfg(feature = "rm2")]
const SCREEN_H: u32 = 1872;

#[cfg(not(feature = "rm2"))]
const SCREEN_W: u32 = 1620;
#[cfg(not(feature = "rm2"))]
const SCREEN_H: u32 = 2160;

Il compilatore sceglie il blocco giusto in base alla feature attiva. A runtime il binario non fa nessuna scelta dinamica: la risoluzione è già “cotta dentro”.


Modifica 2 - la penna (pen.rs): il problema più complesso

Questa è la parte con più sottigliezze, e vale la pena spiegarla per bene.

2a. Dimensione degli eventi evdev

Linux comunica gli eventi della penna (pressione, coordinate, …) tramite il sottosistema evdev. Ogni evento è una struct C con tre campi: timestamp, tipo, codice, valore. Su un sistema a 64 bit quella struct occupa 24 byte; su un sistema a 32 bit (come l’i.MX7 del reMarkable 2) occupa 16 byte.

Il codice originale leggeva blocchi da 24 byte - sul rm2 leggeva dati sfasati, producendo valori di pressione e coordinate completamente casuali.

La fix introduce due costanti condizionali:

#[cfg(feature = "rm2")]
const EV_SIZE: usize = 16;
#[cfg(not(feature = "rm2"))]
const EV_SIZE: usize = 24;

2b. Riconoscimento del dispositivo penna

Il codice cercava il dispositivo marker in /proc/bus/input/devices. Il Wacom del rm2 si chiama wacom. Il riconoscimento accetta ora entrambi i nomi.

2c. Range del digitalizzatore

Sul rm2 l’asse lungo (corrispondente all’altezza dello schermo) ha range 0–20966, quello corto 0–15725 - valori completamente diversi dal Paper Pro. Le costanti DIGI_MAX_X e DIGI_MAX_Y sono anch’esse condizionali via rm2.

2d. Il digitalizzatore è ruotato di 90°

Questo è il problema più insidioso. Sul reMarkable 2, il sensore Wacom fisico è montato ruotato di 90° rispetto allo schermo. Quando muovi la penna verso destra sulla pagina, il digitalizzatore registra un incremento sull’asse Y, non sull’asse X.

La funzione map_to_screen traduce le coordinate raw del digitalizzatore in pixel sullo schermo. Con la feature rm2:

  1. Gli assi sono scambiati: screen_x viene calcolato da raw_y, e screen_y da raw_x.
  2. L’asse verticale è invertito (INVERT_Y = true): l’asse lungo del Wacom cresce dal basso verso l’alto, mentre i pixel dello schermo crescono dall’alto verso il basso.

Senza questa rotazione l’inchiostro appariva in posizioni completamente sbagliate (lontano dalla penna, specchiato, capovolto).

2e. Soglia di pressione configurabile

Sul Paper Pro si usava una soglia fissa di 40 per distinguere la scrittura dall’hover (penna vicina allo schermo ma senza toccare). Sul rm2 il Wacom usa il flag touch come gate principale: quando la penna è in hover, touch=false e pressure=0 - quindi una soglia alta è controproducente e taglia l’inizio dei tratti reali.

La soglia è ora configurabile via variabile d’ambiente, senza ricompilare:

RIDDLE_PEN_MIN_PRESSURE=60 ./riddle

Modifica 3 - il tocco (touch.rs)

Analogamente alla penna, il riconoscimento del dispositivo touch è stato esteso per accettare anche il nome pt_mt (oltre a touch), e usa le stesse costanti EV_SIZE / EV_OFF per leggere gli eventi in modo corretto su 32 bit.


Modifica 4 - il motore e-ink (epfb.cpp)

Questa è la modifica più bassa e più sorprendente. quill è una libreria C++ che intercetta le chiamate al motore e-ink proprietario libqsgepaper.so - tecnicamente è uno shim di interposizione basato su LD_PRELOAD.

Il trucco funziona così: invece di usare la libreria direttamente, quill si inserisce tra il programma e la libreria, intercettando costruttori specifici di QImage (la classe Qt che rappresenta un’immagine in memoria). Quando libqsgepaper.so crea i suoi buffer interni, quill li cattura e li usa per leggere i pixel dell’e-ink.

Sul Paper Pro (64 bit) il costruttore QImage interessato ha la firma C++ mangled _ZN6QImageC1EPhiiiNS_6FormatEPFvPvES2_ con un parametro bytesPerLine di tipo qsizetype (64 bit). Sul rm2 (32 bit) quel tipo diventa int (32 bit), producendo un nome mangled diverso - la libreria non veniva intercettata, i puntatori ai buffer rimanevano a zero, e l’applicazione terminava con:

framebuffer addresses not set (A=0 B=0)

La fix è aggiungere l’intercettazione del costruttore 32-bit accanto a quella 64-bit, così entrambi i dispositivi vengono coperti.


Modifica 5 - gli script di build

Tre script sono stati resi parametrici rispetto alla variabile DEVICE:

  • riddle/build-takeover.sh: DEVICE=rm2 seleziona il target Rust armv7-unknown-linux-gnueabihf, l’SDK rm2, e le feature takeover,rm2. Il default (rmpp) è invariato.
  • quill/build.sh: rispetta la variabile SDK per puntare all’SDK corretto.
  • riddle/scripts/make-bundle.sh: usa DEVICE per trovare il binario nel path di output corretto.

Compilare per rm2 diventa quindi:

cd quill
SDK=~/rm-sdk-rm2 ./build.sh

cd ../riddle
DEVICE=rm2 ./build-takeover.sh
# output: target/armv7-unknown-linux-gnueabihf/release/riddle-takeover

Perché serve l’SDK reMarkable

Il reMarkable 2 usa una versione di glibc specifica, le librerie Qt del vendor (libqsgepaper.so) sono compilate contro quella versione, e il binario prodotto deve girare su quel sistema. Il compilatore C/C++ standard di Ubuntu usa la glibc del tuo PC - le versioni non combaciano.

L’SDK ufficiale reMarkable per ARMv7 fornisce un cross-compilatore già configurato con il sysroot giusto (cortexa7hf-neon-remarkable-linux-gnueabi). È scaricabile dal portale developer di reMarkable e va installato una volta sola (es. in ~/rm-sdk-rm2).


Disclaimer: log per la diagnostica

Durante il porting sono stati aggiunti log temporanei per capire cosa stava succedendo:

  • [quill-diag] in epfb.cpp - stampava i parametri dei costruttori QImage intercettati, permettendo di vedere quale firma veniva (o non veniva) catturata.
  • RIDDLE_PEN_DEBUG=1 in pen.rs - registra su stderr le coordinate raw, quelle mappate sullo schermo, la pressione e il flag touch per ogni evento.

Questi log sono utili nella fase di bring-up, ma vanno rimossi dal codice prima di una release (o lasciati come opzione opzionale, nel caso del debug penna).

Per misurare i valori reali della penna sul proprio tablet, prima di rimuovere il debug:

RIDDLE_PEN_DEBUG=1 ./riddle 2>/tmp/pen.log
# scrivi normalmente, poi esci
grep "pressure=" /tmp/pen.log

ProblemaFileTecnica
Risoluzione schermoriddle/src/fb.rscostanti #[cfg(feature = "rm2")]
Range digitalizzatoreriddle/src/pen.rscostanti condizionali
Nome dispositivo pennariddle/src/pen.rsmatch su stringa
Dimensione evento evdevriddle/src/pen.rs, touch.rsEV_SIZE condizionale
Rotazione 90° del Wacomriddle/src/pen.rsswap assi + INVERT_Y
Soglia pressioneriddle/src/pen.rs, main.rsvariabile d’ambiente
Nome dispositivo touchriddle/src/touch.rsmatch su stringa
Costruttore QImage 32-bitquill/src/epfb.cppintercettazione aggiuntiva
Target / SDK di buildbuild-takeover.sh, build.shvariabile DEVICE

Tutto il lavoro specifico per il rm2 è contenuto e reversibile. La codebase originale per il Paper Pro non è stata toccata - solo estesa.


Come farlo partire

Per chi vuole provare il porting, ecco i passi principali:

Compilare takeover

All’interno del progetto, nella cartella riddle, compilare con:

cd riddle
DEVICE=rm2 ./build-takeover.sh
# output simile: target/armv7-unknown-linux-gnueabihf/release/riddle-takeover

Poi, collegando il tablet via SSH, copiare il binario sul dispositivo:

scp -O target/armv7-unknown-linux-gnueabihf/release/riddle-takeover root@10.11.99.1:/home/root/riddle/riddle

E adesso arriva il bello: per eseguire il takeover, bisogna fermare il processo remarkable e lanciare riddle-takeover al suo posto. Questo richiede privilegi di root e un po’ di attenzione, perché si sta sostituendo il processo principale del tablet.

Apriamo quindi due terminali: uno lo useremo come cuscinetto di emergenza, l’altro per lanciare il takeover. In quello di emergenza, eseguire:

ssh root@10.11.99.1

E lasciamolo aperto. Nell’altro terminale, sempre via SSH, eseguire:

ssh root@10.11.99.1
# e poi..

cd /home/root/riddle
export LD_LIBRARY_PATH=/home/root/riddle:/usr/lib/plugins/scenegraph
systemctl stop xochitl
./riddle

Quest’ultima sequenza di comandi ferma il processo xochitl (l’interfaccia utente del tablet) e lancia riddle. Se tutto va bene, il tablet si trasforma in un diario magico controllato da LLM.

Nota: la penna ha la punta rotta, e questo è il motivo per cui nella demo il tratto appare sbavato. Con una penna funzionante, l’inchiostro appare perfettamente nitido.


Risorse utili

Conosci meglio chi ha scritto questo articolo

Serena Sensini

Ciao! Mi chiamo Serena Sensini e sono la creatrice di @ TheRedCode.it. Ho aperto questo blog nel 2021 per raccontare il mio lavoro e il mondo dell’informatica a parole semplici, in piccole pillole e alla portata di tutte le persone.

Sono un’ingegnera informatica specializzata in ambito AI & NLP. Di giorno lavoro come CTO @ Welyk e come Innovation & Emerging Technologies Leader @ Dedalus, mentre di notte scrivo e sono autrice di 5 libri -per ora-. 🖊️

Foto di Serena Sensini

Partners

Community, aziende e persone che supportano attivamente il blog

Vuoi diventare tech content creator? 🖊️

Se ti va di raccontare la tua esperienza nel mondo tech, questo è il posto giusto.

Cerchiamo voci autentiche, esempi pratici e punti di vista utili per chi legge.

Scrivici a collaborazioni[at]theredcode.it con una proposta: idea, taglio del contenuto e una breve presentazione. Non vediamo l'ora di leggere la tua esperienza!

Invia la tua idea