Skip to main content
The hub server is a standalone Node.js service designed to run on a Raspberry Pi (or any ARM64/AMD64 machine) inside the restaurant’s local network. It coordinates all POS terminals via WebSocket, persists state in SQLite, and forwards events to Odoo in the background. Package: nu_pos_hub/

Startup sequence

nu_pos_hub/src/index.ts initializes all components in order:
  1. Load configuration from environment
  2. Initialize SQLite database (WAL mode)
  3. Create storage layers (orders, leases, events, tables, table locks)
  4. Create lease manager with revoke callback
  5. Create table lock manager with revoke callback
  6. Create Odoo adapter and forwarder
  7. Wire up message handlers
  8. Start HTTP server (health, status, events, dashboard)
  9. Start WebSocket server (attached to HTTP server)
  10. Start periodic tasks (lease tick, forwarder, status broadcast)
  11. Start mDNS advertisement (if enabled)
  12. Register graceful shutdown handlers (SIGTERM, SIGINT)

Configuration

nu_pos_hub/src/config.ts — All settings via environment variables:

Storage layer

All stores use the same SQLite database with WAL mode for concurrent reads.

SQLite schema

SQLite pragmas

Store APIs

Transport-level PING/PONG

The WebSocket server handles {"type":"PING"} messages at the transport level before routing to protocol handlers. It responds immediately with {"type":"PONG"}. This keeps connections alive through proxies and NAT without requiring protocol-level message definitions.

Message handlers

Auth handler

handlers/authHandler.ts
  1. Receives raw WebSocket + AUTH message
  2. Validates session token against Odoo: POST /pos-react/api/session with the token as cookie
  3. On success: creates TerminalInfo, registers in terminal registry, sends AUTH_OK
  4. On failure: sends AUTH_FAIL, connection is not registered

Lease handler

handlers/leaseHandler.ts — Delegates to LeaseManager (see leasing):
  • LEASE_ACQUIREleaseManager.acquire() → sends LEASE_GRANTED or LEASE_DENIED
  • LEASE_RELEASEleaseManager.release() → appends lease_release event
  • LEASE_HEARTBEATleaseManager.heartbeat() → renews lease expiry

Order handler

handlers/orderHandler.ts — The core sync handler:
  1. Verify terminal holds lease for posReference
  2. Check baseVersion >= storedVersion (else send CONFLICT)
  3. Upsert order in SQLite (version increments)
  4. Append order_snapshot event to log (forwarded=0)
  5. Broadcast ORDER_UPDATED to all terminals except sender
  6. If snapshot.tableId is set: update table status (occupied, or available if paid)

Table lock handler

handlers/tableLockHandler.ts — Delegates to TableLockManager (see table locking):
  • TABLE_LOCK_ACQUIRE → grants or denies, broadcasts TABLE_LOCKS_STATE on grant
  • TABLE_LOCK_RELEASE → broadcasts TABLE_LOCK_RELEASED
  • TABLE_LOCK_HEARTBEAT → sends TABLE_LOCK_REVOKED on failure
  • TABLE_LOCK_FORCE_ACQUIRE → Server-side role check → evicts current holder, grants to requester

Floor plan handler

handlers/floorPlanHandler.ts — Broadcasts floor plan changes to other terminals:
  • FLOOR_PLAN_UPDATE → Broadcasts FLOOR_PLAN_CHANGED to all terminals except sender (includes floorId, tables, layoutMode?)

Terminal registry

server/terminalRegistry.ts — In-memory map of connected terminals:
Key methods: register(), remove(), send(), broadcast(), count(), getAll().

Odoo adapter

upstream/odooAdapter.ts — Converts snapshots to Odoo format and forwards.

Snapshot transformation

Voided lines (isVoided: true) are filtered out before forwarding.

Endpoint selection

  • New orders (no odooId): POST /pos-react/api/orders/create
  • Existing orders (has odooId): POST /pos-react/api/orders/update
Both endpoints are idempotent via pos_reference.

HTTP endpoints

Periodic tasks

Graceful shutdown

On SIGTERM or SIGINT:
  1. Stop all periodic timers
  2. Stop forwarder
  3. Stop mDNS
  4. Close HTTP server
  5. Close SQLite database
  6. Exit process

File map