enyDyne API
.md

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