--- type: concept tags: [parking, printer, device, reliability] sources: [] updated: 2026-06-14 --- # Printer roles & failover A lane runs **more than one printer**, and the system knows each one's job so it can fail over automatically. This is a reliability decision, not a threat-model one: an entry ticket must still print when the outside dispenser jams or drops off the network. ## Roles Each printer instance (a `devices` row, category `printer`) declares a **role** in its config: - **`entry-dispenser`** — outside, at the lane. Prints the entry ticket the driver takes. - **`booth-receipt`** — inside the booth. Prints receipts at exit/payment, the booth till's Z-reports and vouchers, AND serves as the **backup** for entry tickets (and for wash slips). - **`wash-desk`** — at the Car Wash desk (added 2026-09-06). Prints the wash till's slips: its Z-report and drawer vouchers (see [[shift]] §Tills). Nothing else ever prints here. It also declares a **`failoverRank`** (higher = preferred within a role) to order multiple printers of the same role deterministically (ties broken by id). ## Failover rule (asymmetric, on purpose) For an **entry ticket** (`wantRole = entry-dispenser`): try the entry dispensers (best rank first), then fall back to the **booth printer**. So a driver still gets a ticket when the outside unit is offline — the operator hands it over from the booth. The reverse is **deliberately not** done: a **receipt** never prints on the outside dispenser. Receipts are a booth-only job; an entry dispenser falling back to print receipts makes no physical sense. For a **wash slip** (`wantRole = wash-desk`): the desk printers first, then the **booth printer** — a site that has not bought a desk printer keeps printing the wash till's Z-report and vouchers in the booth, exactly as it did before the role existed. Nothing falls back *to* the wash desk: a booth receipt or an entry ticket never prints there. `ShiftService` resolves the role from the till (`TILL_PRINTER_ROLE`: booth → `booth-receipt`, carwash → `wash-desk`) and keeps one legacy fallback — a booth with a single printer that carries no booth role still prints its slips on it. `printerRoleOf(config)` is the one reading of the saved `role` field, so every loader (entry flow, booth receipts, shift slips) agrees on what a printer is; the device footer shows a desk printer as "at wash desk". ## Where the logic lives - The driver (`rongta`) is **role-agnostic** — role/rank are just config; the transport doesn't care. Keeps [[device-adapter-pattern|adapters]] swappable. - Selection is pure logic in `packages/devices/printer-routing.ts`: `orderForRole()` ranks candidates; `printWithFailover()` attempts the print down the list and throws `NoPrinterAvailableError` only when every candidate fails. - It **attempts the print directly** rather than racing a `healthCheck` first — the print is the real reachability test, and a health probe that passes can still be followed by a failed print. ## Open: the all-printers-down policy When `printWithFailover` exhausts every candidate, what should entry do — raise the barrier with no paper ticket (the plate/[[lpr-camera]] is the independent record), or hold? That policy belongs to the **entry flow** ([[device-input-flow]], [[fail-state-safety]]), not the printer layer, and is **not yet decided**. The signed event ([[append-only-event-chain]]) is created regardless of whether paper prints.