Hoe je een f**king printer bouwt

Ik was verkocht.

Toen het arriveerde, was ik tevreden over hoe plat hij was; hij voelde stevig aan in mijn hand. Ik begon dingen toe te voegen aan de CrossPoint-firmware: een andere boot-animatie, dobbelstenen die ik kon werpen door de reader te schudden, en een LinkedIn-QR-code voor netwerkevenementen in San Francisco.

Maar het uploaden van bestanden was tedious. Ik moest verbinden met de hotspot van het apparaat en een kleine uploadwebsite in mijn browser openen.

Vreselijk.

Terwijl ik zocht naar een fijnere manier om bestanden te versturen, schoot me een gedachte te binnen: als het eruitziet als papier, moet het zich ook gedragen als papier. Ik zou er op moeten kunnen printen.

Wat maakt een printer een printer?

Ik wilde iets op mijn MacBook kunnen openen, op 'Print' drukken en de Xteink kunnen selecteren. Dat betekende dat ik moest uitzoeken wat mijn computer aan de andere kant verwachtte te vinden.

Dit leidde me naar het Internet Printing Protocol (IPP). Hiermee kan een computer aan een printer vragen wat deze ondersteunt, een document indienen en vragen wat de status van de printopdracht is. De berichten worden via HTTP verzonden. Een operatie zoals Get-Printer-Attributes vraagt naar de mogelijkheden; Print-Job verzendt het werk.

Ik configureerde de printer voor monochrome output, 300 dpi, één kopie en enkelzijdig printen. Voor de documentformaten accepteerde ik Apple raster en PWG raster. Dit betekende dat de Mac het document in pixels moest omzetten voordat het verzonden werd; de 'penguin' zou het resultaat vervolgens verkleinen om op het scherm te passen.

Ik definieerde A5 en Letter als papierformaat, het mediatype als stationery, en een output-bak genaamd face-up. Ik noemde het geheel 'penguin' en gaf het het bijpassende kleurenschema.

Ontdekking en configuratie

Ik gebruikte Bonjour om een ipp.tcp-service onder die naam aan te kondigen. De advertentie bevatte de formaten die ik accepteerde en het adres waar de printopdrachten naartoe moesten gaan.

Om driverloze detectie op macOS mogelijk te maken, moest ik ook het universal subtype toevoegen. Dat betekende dat ik direct de mDNS-API van ESP-IDF moest aanroepen, omdat de Arduino-wrapper dit niet ondersteunde.

Het computer laten verzenden van een pagina was echter slechts een deel van de klus. Ik moest de pagina ook daadwerkelijk op het apparaat kunnen ontvangen.

De uitdaging: Geheugenbeperkingen

Waar bewaar ik de pagina?

Een Letter-pagina bij 300 dpi is 2.550 × 3.300 pixels. Bij één byte per grijswaardenpixel is dat ongeveer 8,4 MB ongecomprimeerd. De X3 heeft echter slechts 400 KB RAM, waarvan 16 KB is gereserveerd voor cache. Ik moest Wi-Fi draaien, een printerserver draaien en op de een of andere manier een hele f**king pagina ontvangen.

Wanneer de Wi-Fi actief was en het beeld van de printerpagina was toegewezen, hield ik slechts 6,8 KB aan heap-geheugen over.

Ik herinnerde me mmap op Linux. Zou ik iets soortgelijks kunnen doen met de SD-kaart en doen alsof ik meer RAM had? De geheugenmapping-ondersteuning van de C3 was echter bedoeld voor flashgeheugen, niet voor bestanden op de SD-kaart.

De oplossing: Een transformatie-pijplijn

Maar wacht. Kon ik het display zelf als opslag gebruiken?

Wat als ik de binnenkomende pagina door een transformatie-pijplijn leidde en het resultaat direct naar het display schreef? De pixels decoderen, ze verkleinen om te passen, en ze ditheren naar zwart-wit. Zodra een rij klaar was, plaatste ik deze direct op de juiste plek op het display en hergebruikte ik de werkruimte. Dit proces herhaalde ik tot de pagina compleet was.

Het display had al RAM gereserveerd voor het schermbeeld. Ik kon de pagina daar direct opbouwen terwijl deze binnenkwam. Tot die tijd had ik een volledig tweede beeld opgebouwd om dit vervolgens te kopiëren.

Mijn decoder werkte al rij voor rij. Ik paste de scaler aan zodat deze ook voltooide rijen doorgaf en koppelde deze aan het schermbeeld van het display. In het begin liet ik de pagina in banden verschijnen, zoals papier dat uit een printer komt. Elke tussenliggende refresh duurde ongeveer een halve seconde, dus ben ik overgestapt op het in één keer tonen van de voltooide pagina.

De voltooide pagina sloeg ik op als een BMP-bestand op de SD-kaart met behulp van de bestaande screenshot-writer.

Image-buffer RAM

VoorheenNu
Van de 400 KB van de chip~113 KB~62 KB

Hierdoor kreeg de netwerkstack meer ruimte voor de socket-buffers.

Het resultaat

Ik had om een of andere reden een voorbeeld van een manga-afbeelding uit Mushoku Tensei op mijn MacBook staan. Ik opende deze in Preview en ging op printen.

Daar stond 'penguin' in de printerlijst.

Holy shit.

Ik selecteerde hem en printte. De mangapagina zag er erg goed uit op de Xteink. Voor zover ik me herinner, duurde het ongeveer een seconde voordat hij verscheen. Hij staat er nog steeds.

Ik heb één avond besteed om tot die eerste print te komen. De printerserver draait op de reader zelf. Ik kan hem verbinden met een Wi-Fi-netwerk of zijn eigen hotspot starten: literate-penguin.

Mijn penguin kan nu lezen.

De code staat in mijn CrossPoint-fork, inclusief de printer-implementatie. Opgeslagen prints blijven op de SD-kaart staan, en ik kan ze op de reader bekijken. Mijn printer heeft na alles toch een output-lade. Het is een map.