Qnack + qnack-mqtt-print: een web-POS overbruggen naar thermische kassabonprinters
Qnack is een webgebaseerd point-of-sale voor horeca; qnack-mqtt-print is de kleine Node-service die de browser overbrugt naar thermische kassabonprinters via MQTT. De architectuur, het MQTT-topic-ontwerp en de faalmodi die we moesten afhandelen.
Een browsergebaseerd POS dat via USB praat met een thermische kassabonprinter is een non-starter: USB-toegang vanuit een browsertabblad is beperkt, hostiele omgevingen installeren geen drivers, en de printer staat mogelijk in de keuken terwijl de bestelling aan de bar wordt opgenomen. We hadden een transport nodig dat de browser kon bereiken en dat een kleine headless-service native kon spreken. MQTT was het antwoord.
De architectuur
Drie componenten, elk minimaal:
- Qnack web-POS. Een Next.js-app die draait op een tablet aan de bar. Bestelregels sturen een HTTPS-verzoek naar de Qnack-backend met de bestellingspayload.
- Qnack-backend (NestJS). Bewaart de bestelling, zendt een "print"-event uit op een MQTT-topic dat is afgebakend tot de vestiging.
- qnack-mqtt-print. Een kleine Node-service die draait op een Raspberry Pi naast de printer. Abonneert op het vestigingsgebonden topic, formatteert de bestelling naar de ESC/POS-byte-stream van de printer en schrijft die over USB naar de printer.
Waarom MQTT, geen WebSocket
WebSocket had gewerkt, maar de keuken en de bar zitten op hetzelfde fysieke netwerk als de printer — en niet op hetzelfde netwerk als de cloudbackend. MQTT-brokers kunnen lokaal op de Pi draaien, achter een firewall, zonder inkomende poorten vanaf het internet. De bestelling overleeft een backend-deploy, een korte internetstoring of een tablet-reboot. De publish is local-first, duurzaam over reconnects en ge-acknowledgeerd bij de broker.
Topic-ontwerp dat schaalt
Het topic-ontwerp is het onderdeel waar de meeste teams te weinig in investeren. Het onze:
qnack/{vestigingsId}/printers/{printerId}/jobs— de wachtrij voor een specifieke printer in een specifieke vestiging.qnack/{vestigingsId}/printers/{printerId}/status— last-will-and-testament-heartbeat, zodat de POS "keukenprinter offline" kan tonen als de Pi stopt met heartbeaten.
Een nieuwe vestiging is een nieuwe branch in de topic-boom. De broker handhaaft toegangscontrole per vestiging, zodat een verkeerd geconfigureerde subscriber bij vestiging A de bestelstream van vestiging B niet kan lezen.
Faalmodi die we moesten afhandelen
- Pi-powercycle. De MQTT-client herverbindt bij boot, het last-will-and-testament publiceert een "printer offline"-bericht en de POS stopt met het in de wachtrij plaatsen van bestellingen voor deze printer.
- Printer zonder papier. De ESC/POS-driver retourneert een statusbyte. qnack-mqtt-print publiceert een "printer error"-bericht op het status-topic, en de POS toont een banner.
- Backend onbereikbaar maar POS online. De Qnack-POS plaatst bestellingen lokaal in de wachtrij en probeert het opnieuw met backoff. De keukenprinter krijgt ze zodra de backend herstelt.
Wat ons dit leerde
De les generaliseert voorbij POS: wanneer je browser-naar-fysiek-apparaat-communicatie nodig hebt, is de brugservice het saaie onderdeel dat iedereen onderschat. Bouw het alsof het elke frontend-rewrite overleeft — want dat zal het.
