Qnack + qnack-mqtt-print : un POS web relié aux imprimantes thermiques via MQTT
Qnack est un point-of-sale web pour la restauration ; qnack-mqtt-print est le petit service Node qui fait le pont entre le navigateur et les imprimantes de tickets thermiques via MQTT. L'architecture, le design des topics MQTT et les modes d'échec que nous avons dû gérer.
Un POS basé navigateur qui parle via USB à une imprimante thermique de tickets est un non-starter : l'accès USB depuis un onglet de navigateur est limité, les environnements hostiles n'installent pas de drivers, et l'imprimante est probablement en cuisine alors que la commande est prise au bar. Il nous fallait un transport que le navigateur pouvait atteindre et qu'un petit service headless pouvait parler nativement. MQTT était la réponse.
L'architecture
Trois composants, chacun minimal :
- Qnack web POS. Une app Next.js qui tourne sur une tablette au bar. Les lignes de commande envoient une requête HTTPS vers le backend Qnack avec le payload de la commande.
- Qnack backend (NestJS). Persiste la commande, publie un événement « print » sur un topic MQTT limité à l'établissement.
- qnack-mqtt-print. Un petit service Node qui tourne sur un Raspberry Pi à côté de l'imprimante. S'abonne au topic limité à l'établissement, formate la commande vers le flux d'octets ESC/POS de l'imprimante et l'écrit sur USB vers l'imprimante.
Pourquoi MQTT, pas WebSocket
WebSocket aurait fonctionné, mais la cuisine et le bar sont sur le même réseau physique que l'imprimante — et pas sur le même réseau que le backend cloud. Les brokers MQTT peuvent tourner en local sur le Pi, derrière un pare-feu, sans ports entrants depuis Internet. La commande survit à un déploiement backend, à une coupure internet brève ou à un reboot de tablette. Le publish est local-first, durable à travers les reconnexions et acquitté au broker.
Design de topic qui scale
Le design de topic est la partie dans laquelle la plupart des équipes investissent trop peu. Le nôtre :
qnack/{établissementId}/printers/{printerId}/jobs— la file d'attente pour une imprimante spécifique dans un établissement spécifique.qnack/{établissementId}/printers/{printerId}/status— last-will-and-testament-heartbeat, pour que le POS puisse afficher « imprimante cuisine hors ligne » quand le Pi arrête de heartbeat.
Un nouvel établissement est une nouvelle branche dans l'arbre de topics. Le broker applique le contrôle d'accès par établissement, pour qu'un subscriber mal configuré sur l'établissement A ne puisse pas lire le flux de commandes de l'établissement B.
Modes d'échec que nous avons dû gérer
- Coupure de courant du Pi. Le client MQTT se reconnecte au boot, le last-will-and-testament publie un message « imprimante hors ligne » et le POS arrête de mettre les commandes en file pour cette imprimante.
- Imprimante sans papier. Le driver ESC/POS retourne un octet de statut. qnack-mqtt-print publie un message « printer error » sur le topic status, et le POS affiche un bandeau.
- Backend inatteignable mais POS en ligne. Le POS Qnack met les commandes localement en file et retente avec backoff. L'imprimante cuisine les reçoit quand le backend récupère.
Ce que cela nous a appris
La leçon se généralise au-delà du POS : quand vous avez besoin d'une communication navigateur-vers-appareil physique, le service de pont est la partie ennuyeuse que tout le monde sous-estime. Construisez-le comme s'il survivait à chaque réécriture frontend — parce qu'il le fera.
