Printer Client
cmd/printer polls restaurant orders and prints through file or direct USB output. It targets 32-bit ARM Linux. USB mode uses libusb and ESC/POS commands.
Installation
Run installer as regular Raspberry Pi user:
curl --proto '=https' --tlsv1.3 -sSf https://docs.enydyne.de/scripts/install-printer.sh | sh
Installer downloads latest ARMv7 binary, verifies server-provided SHA-256, installs it to $HOME/.local/bin/printer, generates config when missing, and enables system service for startup. It prints config path to edit and reboot command when complete. USB support is statically linked, so target needs no libusb package. sudo is used only for service installation.
Installer uses https://app.enydyne.de by default. For another deployment, download script and pass server origin as first argument or set ENYDYNE_ORIGIN.
Configuration
Config path is $XDG_CONFIG_HOME/enydyne/print/config.yaml. Without XDG_CONFIG_HOME, client uses $HOME/.config/enydyne/print/config.yaml; without HOME, it uses /etc/enydyne/print/config.yaml.
Generate example config at resolved path:
printer init
Command prints only absolute created path to stdout. Errors go to stderr. It refuses to overwrite existing config. Edit server URL, output directory, query names, and API keys before starting client.
server_url: https://app.enydyne.de/
poll_interval: 10s
printer:
mode: file
device: /var/spool/enydyne-print
character_size: 1 # Reserved; any value is ignored in file mode.
max_width: 42
queries:
- name: restaurant-north
api_key: eny_replace_me
- name: restaurant-south
api_key: eny_replace_me_too
server_url is server origin, not GraphQL path, and defaults to https://app.enydyne.de/. Override it for development; loopback HTTP such as http://127.0.0.1:4321 is accepted. Client appends /api/graphql. Each API key selects one restaurant. Query names must be unique and may contain letters, digits, _, and -.
In file mode, printer.device must be absolute directory path. max_width must be between 1 and 1024 and limits each output line by Unicode character count. character_size does not affect file mode.
Direct USB
USB mode discovers a USB Printer Class interface by vendor and product ID, claims its bulk OUT endpoint, sends one ESC/POS receipt, then closes connection. Each print reconnects, so unplugged printer can recover on later poll. Example for printer from reference driver:
printer:
mode: usb
vendor_id: 0x0416
product_id: 0x5011
character_size: 1
max_width: 42
vendor_id and product_id must both be non-zero. character_size accepts 1 through 8 and scales both width and height through ESC/POS GS !; rendered line width is max_width / character_size. Printer must accept UTF-8 receipt text and ESC/POS initialize, character-size, and partial-cut commands.
Service user needs raw USB-device permission. Create udev rule matching configured IDs, replacing values and group when needed:
printf '%s\n' 'SUBSYSTEM=="usb", ATTR{idVendor}=="0416", ATTR{idProduct}=="5011", MODE="0660", GROUP="'"$(id -gn)"'"' | sudo tee /etc/udev/rules.d/70-enydyne-printer.rules >/dev/null
sudo udevadm control --reload-rules
sudo udevadm trigger
USB mode stages receipt under data directory printed/ before sending and keeps successful receipt copies there. These records prevent repeat output from overlapping polls. Failures before USB transfer are retried after automatic pending-record cleanup. Transfer errors or process interruption during send leave .pending record because printer outcome cannot be known. Client blocks that receipt instead of risking duplicate but continues later orders without advancing checkpoint. Check paper, then delete pending record to retry, or rename it without .pending suffix to confirm receipt printed.
Protect config because it contains API keys. For default per-user path:
chmod 600 "${XDG_CONFIG_HOME:-$HOME/.config}/enydyne/print/config.yaml"
When neither XDG path nor HOME exists, protect global config with chmod 600 /etc/enydyne/print/config.yaml.
Data
Logs, process lock, and polling checkpoint live under $XDG_DATA_HOME/enydyne/print. Fallbacks are $HOME/.local/share/enydyne/print and then /var/lib/enydyne/print when HOME is unset.
Keep generated text files in output directory. Their stable names provide duplicate protection when process stops after writing an order but before saving its checkpoint. Moving or deleting files removes this protection for overlapping polls.
Client re-queries one polling interval to tolerate late visibility and modest clock skew. Host still needs working time synchronization. It catches up in ranges no larger than 31 days and subdivides ranges containing over 1,000 orders.
Updates
Printer checks client registry before first order poll and every hour afterward. It compares SHA-256 of running /proc/self/exe with latest printer-linux-armv7 metadata from configured server_url. On mismatch it downloads update, validates size and checksum, keeps current binary as printer.previous, atomically replaces executable, and restarts itself.
If restart itself fails, printer automatically restores printer.previous. Backup also remains available for manual recovery if a checksum-valid update starts but later fails.
Update checks, downloads, GraphQL polling, and printing run in one sequential event loop. Due update always runs before another poll, and update waits for any current poll to finish, so print jobs and GraphQL requests never overlap binary replacement. Installer-managed $HOME/.local/bin/printer is writable by service user and supports self-update.
Build
Production binary can be queried and downloaded from client registry:
GET /api/clients/printer/linux/armv7/latest
GET /api/clients/printer/linux/armv7/latest/download
Local cross-build requires ARM hard-float GCC, ARM libusb development files, and matching pkg-config search path. Debian example:
sudo dpkg --add-architecture armhf
sudo apt-get update
sudo apt-get install gcc-arm-linux-gnueabihf libc6-dev-armhf-cross libudev-dev:armhf libusb-1.0-0-dev:armhf pkg-config
PKG_CONFIG_LIBDIR=/usr/lib/arm-linux-gnueabihf/pkgconfig:/usr/share/pkgconfig \
CGO_ENABLED=1 CGO_LDFLAGS='-Wl,--as-needed -Wl,-Bstatic -lusb-1.0 -Wl,-Bdynamic -ludev -latomic -pthread' CC=arm-linux-gnueabihf-gcc \
GOOS=linux GOARCH=arm GOARM=7 go build -tags netgo,osusergo \
-ldflags '-s -w -linkmode external' -o printer ./cmd/printer