Think of a busy restaurant with tablets at the front door, waiters walking around with handhelds, a cashier terminal, and a kitchen screen. Every device needs to see the same orders in real time. When a waiter adds a burger to Table 5, the kitchen screen needs to know immediately — and so does the cashier. That’s the problem the sync protocol solves: keeping every device in sync, without letting two people step on each other’s changes.Documentation Index
Fetch the complete documentation index at: https://docs.laportenard.com/llms.txt
Use this file to discover all available pages before exploring further.
The three pieces
Terminals (the students)
Tablets and computers running the POS app. Each is a “terminal.” They take orders, update tables, and process payments.
Hub (the teacher)
A Node.js server running on-site (Raspberry Pi). Every terminal connects via WebSocket. The hub decides who edits what, stores every order, and broadcasts updates.
Odoo (the principal's office)
The cloud business system. The hub batches order updates to Odoo every 5 seconds. Odoo holds the permanent record; the hub handles real-time traffic locally.
Step by step
Logging in (AUTH)
A terminal starts up and sends
AUTH { token, deviceId }. The hub validates the token against Odoo and replies with AUTH_OK or AUTH_FAIL. On success, the hub immediately sends a SYNC_INIT message — a full dump of all active orders so the terminal starts with accurate data.Locking a table (TABLE_LOCK)
Before a waiter can work a table, their terminal asks:
TABLE_LOCK_ACQUIRE { tableId }. The hub checks:- Free →
TABLE_LOCK_GRANTED - Held by another terminal →
TABLE_LOCK_DENIED { lockedBy }
TABLE_LOCK_HEARTBEAT every 15 seconds to keep the lock alive. If a terminal crashes and stops heartbeating, the lock auto-expires after 45 seconds. A manager can force-take any lock with TABLE_LOCK_FORCE_ACQUIRE.Editing an order (LEASE)
Once on the order page, the terminal needs a lease — like borrowing a library book. It sends
LEASE_ACQUIRE { posReference, baseVersion }. The hub grants it with a version number and expiry time. Only the lease holder can send snapshots for that order.The terminal heartbeats every 20 seconds (LEASE_HEARTBEAT). The lease expires after 60 seconds without one.Saving changes (ORDER_SNAPSHOT)
Every edit — adding an item, changing quantity, applying a discount — sends the complete current order as
ORDER_SNAPSHOT. The hub:- Checks the version number matches (no concurrent edits slipped through)
- Saves the snapshot and bumps the version
- Broadcasts
ORDER_UPDATEDto all other connected terminals
CONFLICT message with its authoritative snapshot.Releasing the lease
When the waiter navigates away or the order is paid, the terminal sends
LEASE_RELEASE { posReference, finalSnapshot? }. The hub saves the final snapshot (if provided) and frees the order for other terminals.Forwarding to Odoo
Every 5 seconds, the hub drains up to 20 recent order events and forwards them to Odoo’s
/pos-api/v1/orders/{create,update} endpoints. Order creation is idempotent by pos_reference — re-forwarding is always safe. If Odoo is temporarily down, the hub keeps the event log and retries.What’s in an order snapshot
AnORDER_SNAPSHOT is a complete photo of an order at a point in time:
| Field | Content |
|---|---|
| Identity | posReference, id, odooId |
| Assignment | tableId, partnerId |
| Status | open / sent / changed / paid |
| Lines | Each item: product, qty, price, discount, taxes, void info, kitchen sent time |
| Payments | Payment method, amount, change |
| Totals | Subtotal, tax, total |
| Meta | updatedAt, terminalId, notes |
Table status
Tables sync separately from orders. ATABLE_UPDATE message sets a table’s status (available, occupied, or reserved) and the hub broadcasts TABLE_UPDATED to all terminals instantly.
Why this design
Without the protocol:- Two waiters could edit the same order — one would silently overwrite the other
- A crashed terminal could hold a lock forever, blocking the whole table
- The kitchen would miss order updates
- The cashier would see stale totals
| Problem | Solution |
|---|---|
| Concurrent edits | Order leases — only one writer at a time |
| Table conflicts | Table locks — only one terminal per table |
| Stale data | Broadcast on every change — all terminals update instantly |
| Crashed terminals | Heartbeat expiry — locks and leases auto-release |
| Odoo downtime | Hub event log — changes queue and forward when Odoo recovers |
Key files
| File | Purpose |
|---|---|
nu_pos_sync_protocol/src/messages.ts | All message types (Terminal→Hub and Hub→Terminal) |
nu_pos_sync_protocol/src/snapshots.ts | OrderSnapshot, TableStatus schemas |
nu_pos_sync_protocol/src/constants.ts | TTLs, heartbeat intervals |
nu_pos_sync_protocol/src/validators.ts | Zod schemas for runtime validation |
nu_pos_hub/ | Hub server: WebSocket broker, lease manager, table lock manager, Odoo forwarder |
nu_pos_react/src/realtime/ | Frontend transport and sync bridge |