Skip to content

Hardware Setup

raktim edited this page Aug 4, 2026 · 1 revision

Hardware Setup

Barcode scanner

No configuration needed. Any USB barcode scanner that acts as a HID keyboard (i.e. "just types" the barcode followed by Enter — true of the vast majority of consumer scanners) works out of the box. The frontend listens for keystrokes and, based on inter-keystroke timing, tells scanner input apart from a human typing so it never interferes with normal form fields.

Camera barcode & QR scanning

No USB scanner? Every place a barcode can be typed or scanned — POS checkout and the Add/Edit Product form — also has a Scan with camera button that opens a live preview and decodes barcodes/QR codes directly in the browser. On a phone or tablet it requests the back camera by default, with a Flip camera button to switch. This works alongside the USB scanner, not instead of it — either path feeds the same lookup/add-to-cart logic. The camera requests a 1080p frame and continuous autofocus where the device supports it — a small code held close to the lens needs enough resolution and focus to resolve, not just camera permission.

Camera scanning over HTTPS — read this if the camera doesn't open on a second device

Browsers only allow camera access on a secure context: an https:// origin, or localhost on the machine itself. The default setup serves the app over plain http://<lan-ip>:1994, so opening it from a phone/tablet works for everything except the camera — the browser blocks it silently, no permission prompt, before the app can even ask. On the till machine itself this is a non-issue (http://localhost:1994 is always secure); the gap only shows up on a second device over the LAN.

Three fixes, pick whichever fits:

  1. Android / desktop Chrome or Edge — no server change. Visit chrome://flags/#unsafely-treat-insecure-origin-as-secure, add http://<this-machine's-LAN-IP>:1994, enable, restart the browser. Per-browser, per-device; only do this on a network you trust.
  2. iPhone/iPad, or any browser without that flag (Safari has none) — bundled HTTPS front door.
    docker compose --profile https up -d
    Then open https://<this-machine's-LAN-IP> (no :1994) on the device. Caddy self-signs a certificate automatically; every browser shows a one-time "not trusted" warning to tap past, then the camera works. Same app, same login, same data — just an extra reverse-proxy hop. The plain http://<ip>:1994 path keeps working unmodified either way.
  3. Any device, no cert warning, works from outside the LAN too — Cloudflare Tunnel.
    # Free, no account, URL changes on restart
    docker compose --profile cloudflare-quick up -d
    
    # Stable URL on your own domain — needs CLOUDFLARE_TUNNEL_TOKEN in .env
    docker compose --profile cloudflare up -d
    Gets a real, publicly-trusted certificate at the cost of traffic routing through Cloudflare instead of staying fully local.

Barcode & QR label generator

Not every product arrives with a scannable barcode. The Add Product form has a Generate button that produces a structurally valid EAN-13 using GS1's 20–29 restricted/internal-use prefix range — checked against your catalog for uniqueness, retried on collision. From Inventory, the barcode icon on any row opens a Barcode / QR code label, rendered client-side (no network call) — Print label or Download PNG/JPG.

Printing & receipts

Three ways to get a receipt out:

Method How it works Setup needed
Print Opens a formatted receipt, triggers the browser's own print dialog — pick any printer your OS/CUPS knows about Set up the printer once at the OS level, same as for any app
Download PDF Server-generates a real PDF (pure-JS pdfkit, no native deps) None
Print via USB Sends raw ESC/POS bytes straight to a USB thermal printer, no dialog One-time Docker device passthrough, see below

Settings → Receipt → Print automatically after every sale skips the extra click on Print. It does not trigger USB printing (no dialog to skip); use "Print via USB" directly for that, or set Auto-print using → Direct USB.

Direct-USB (ESC/POS) printer setup

On Linux this works by default after docker compose up — no vendor/ product ID to configure. Two transports are tried in order:

  1. Kernel usblp character device (/dev/usb/lp0) — the same path a plain echo "hi" > /dev/usb/lp0 writes to. This is the primary transport because most cheap 80mm thermal printers present a vendor-specific USB class, which a class-based scan would miss but the kernel's own usblp driver already binds.
  2. libusb raw bulk transfer — fallback for printers exposing the standard USB Printer class; detaches the kernel driver first so the interface can be claimed.

Check it works — one click, no sale needed: Settings → Receipt → USB printer check →

  • Check printer reports the kernel printer nodes and any USB printer-class device the backend can see.
  • Print test slip sends a real cut-off test receipt.

Set USB printer paper width (80mm standard / 58mm compact) to match your roll.

What the container needs (already configured in docker-compose.yml, Linux host only): the backend service bind-mounts /dev/bus/usb and grants device_cgroup_rules on two device majors — 189 (usbfs, libusb) and 180 (usblp, the kernel printer driver). This is not privileged: true — the container still can't touch anything else on the host. Nothing fails at compose up when no printer is attached.

Does it need root? Yes, and this is already configured — do not add anything extra. The kernel printer node is owned root:lp and the container mknods it on demand; both require root inside the container. backend/Dockerfile has no USER line and docker-compose.yml sets no user: for the backend on purpose.

If a printer works from a terminal (echo hi > /dev/usb/lp0) but not from the app, or from Windows but not Debian — see Troubleshooting.

Clone this wiki locally