Cihaz API (Device Mesh)
device-mesh modülü, bir kiracının yerel donanımı için sunucu tarafındaki
kayıt, eşleştirme ve komut kuyruğudur: fiş/mutfak yazıcıları, nakit çekmeceler,
yazarkasalar (yazarkasa), POS terminalleri, KDS/bar ekranları, garson/müşteri
tabletleri, çağrı kimliği, barkod okuyucular ve birden çok cihazı aynı anda
sürücüleyen yerel köprü ajanı (local_bridge).
Yönetici panelden bir cihaz yuvası (device slot) oluşturur; bu yuva ekranda 6
haneli bir eşleştirme kodu (pair code) gösterir. Cihaz (ya da local_bridge
ajanı) bu kodu girerek eşleşir ve karşılığında uzun ömürlü bir cihaz token’ı
alır. Sonrasında sunucu, cihaza fiş yazdırma, çekmece açma, mali fiş kesme,
ekranda sipariş gösterme gibi komutları cihaz-başına bir komut kuyruğu
üzerinden iletir.
Tüm uç noktalar /api global ön eki altında, /api/v1/devices/...
yolundadır. Tüm REST yüzeyinde ortak olan taban URL, hata zarfı ve rate-limit
katmanları için API Temelleri sayfasına bakın.
Kimlik realm’leri
device-mesh, tek controller’da iki ayrı kimlik yüzeyi barındırır. Her uç
nokta bunlardan tam olarak birini bekler; yanlış realm 401 döner.
| Realm | Başlık | Kim kullanır | Ne için |
|---|---|---|---|
| Personel JWT | Authorization: Bearer <jwt> | Kiracı yöneticileri (ADMIN / MANAGER) | Yuva oluşturma, komut gönderme, listeleme/emekliye ayırma |
| Cihaz token’ı | Authorization: Device <token> | Cihazın kendisi / local_bridge | Eşleşme, heartbeat, sıradaki komutu çekme, ack |
Cihaz realm’i bilerek Authorization: Device <token> kullanır — Bearer
değil. Böylece kullanıcı oturumları için Bearer’ı temizleyen/yeniden
yazan ara katmanlar cihaz kimliğini bozmaz.
Admin rotaları şube kapsamlıdır ve X-Branch-Id başlığı gerektirir
(aşağıda). Cihaz rotaları (pair, heartbeat, next-command, ack) zaten
kiracı/şube bağlamını taşıyan token’ın kendisiyle doğrulanır — X-Branch-Id
almazlar.
Şube kapsamı — X-Branch-Id
Yuva oluşturma, komut gönderme ve cihaz listesi şube kapsamlıdır: sunucu, aktif
şubeyi X-Branch-Id başlığından çözer.
X-Branch-Id: <branch-uuid>Tek şubeye kısıtlı bir MANAGER yalnızca o şubedeki cihazları sürücüleyebilir —
çağıranın şube kapsamı dışındaki bir cihaz 404 döner. ADMIN kiracı genelinde
çalışır.
Cihaz türleri
kind alanı şu kapalı kümeden bir değerdir:
kind | Açıklama |
|---|---|
receipt_printer | Fiş yazıcısı |
kitchen_printer | Mutfak yazıcısı |
yazarkasa | Yazarkasa (mali / fiscal cihaz) |
pos_terminal | Kart ödeme terminali |
kds_screen / bar_screen | Mutfak / bar ekranı |
tablet_waiter / tablet_customer | Garson / müşteri tableti |
caller_id | Çağrı kimliği cihazı |
scanner | Barkod okuyucu |
local_bridge | Birden çok cihazı sürücüleyen yerel köprü ajanı |
capabilities alanı serbest etiketler içeren bir dizidir (en çok 32 öğe, her
biri ≤ 64 karakter) — örn. yazarkasa + yazıcı + çekmece yeteneklerini taşıyan
bir köprü.
Uç noktalar
| Metot | Yol | Realm | Notlar |
|---|---|---|---|
POST | /api/v1/devices | Personel JWT | Cihaz yuvası oluştur; pair code döner |
GET | /api/v1/devices | Personel JWT | Cihazları listele (branchId, kind, status ile filtre) |
DELETE | /api/v1/devices/:id | Personel JWT (ADMIN) | Cihazı emekliye ayır; token’ı iptal eder |
POST | /api/v1/devices/:id/commands | Personel JWT | Cihaza komut kuyruğa al |
POST | /api/v1/devices/pair | Cihaz (pair code) | Yuvayı pair code ile sahiplen; ham token’ı bir kez döner |
POST | /api/v1/devices/heartbeat | Cihaz token’ı | Keep-alive + telemetri |
GET | /api/v1/devices/next-command | Cihaz token’ı | Sıradaki komutu atomik olarak çek |
POST | /api/v1/devices/commands/:id/ack | Cihaz token’ı | Komut sonucunu bildir |
Eşleştirme akışı
Yönetici bir cihaz yuvası oluşturur
Yuva, mevcut şube kapsamı içinde oluşturulur. Yanıt, 6 haneli pairCode’u ve
süresini içerir.
curl -X POST https://hummytummy.com/api/v1/devices \
-H "Authorization: Bearer $JWT" \
-H "X-Branch-Id: $BRANCH_ID" \
-H "Content-Type: application/json" \
-d '{
"kind": "receipt_printer",
"capabilities": ["escpos", "80mm"],
"model": "Epson TM-T20III",
"ownership": "byo"
}'{
"id": "01J...",
"tenantId": "01J...",
"branchId": "01J...",
"kind": "receipt_printer",
"status": "unprovisioned",
"pairCode": "A4F9K2",
"pairCodeExpiresAt": "2026-06-22T10:10:00.000Z"
}branchId zorunludur. Rota şube kapsamlı olduğundan sunucu bunu X-Branch-Id
başlığından çözer; gövdede branchId vererek de açıkça geçebilirsiniz.
ownership alanı sold / rented / byo (varsayılan byo) değerlerinden
biridir.
Cihaz, pair code ile eşleşir
Cihaz (veya local_bridge ajanı) ekrandaki kodu okur ve eşleşir. Yanıt, yalnızca
bir kez dönen ham token’ı içerir.
curl -X POST https://hummytummy.com/api/v1/devices/pair \
-H "Content-Type: application/json" \
-d '{
"pairCode": "A4F9K2",
"model": "Epson TM-T20III",
"capabilities": ["escpos", "80mm"]
}'{
"deviceId": "01J...",
"tenantId": "01J...",
"branchId": "01J...",
"kind": "receipt_printer",
"token": "018f....base64url",
"tokenExpiresAt": "2026-06-23T10:05:00.000Z",
"capabilities": ["escpos", "80mm"]
}Cihaz token’ını saklar ve heartbeat gönderir
Token bundan sonra her istekte Authorization: Device <token> başlığında
gönderilir. Cihaz düzenli heartbeat atarak çevrimiçi kalır.
curl -X POST https://hummytummy.com/api/v1/devices/heartbeat \
-H "Authorization: Device $DEVICE_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "batteryPct": 100, "agentVersion": "1.0.0", "queueDepth": 0 }'Eşleştirme kuralları, TTL’ler ve kaba kuvvet koruması
- Pair code TTL varsayılan 10 dakikadır (
DEVICE_PAIR_CODE_TTL_MS). Süresi dolan kod ilk denemede atomik olarak temizlenir ve “Pair code expired — request a new one” hatası döner. - Cihaz token’ı TTL varsayılan 24 saattir (
DEVICE_TOKEN_TTL_MS). Token’lar sunucuda sha256 hash’i olarak tutulur; ham token asla saklanmaz, yalnızca eşleşme anında bir kez döner. /pairuç noktası 5 istek/dk/IP ile sınırlıdır — 6 haneli kod ([A-Z0-9], ~2,2 milyar olasılık) için kaba kuvvet bütçesini TTL penceresinde anlamsız kılar.- Pair code tek kullanımlıktır: aynı kodu milisaniyeler arayla iki cihaz
girerse, atomik claim (
updateMany) yalnızca ilkini başarılı sayar; ikincisi “Pair code already claimed by another device or expired” hatası alır.
Cihaz durumları
unprovisioned → (yuva oluşturuldu, kod bekliyor)
paired → (ilk eşleşme, henüz heartbeat yok)
online → (heartbeat penceresi içinde)
offline → (heartbeat ~45sn'den uzun süredir gelmiyor)
retired → (yönetici emekliye ayırdı; token iptal)Heartbeat penceresi 45 sn’dir; arka plan süpürücüsü (cron) bu süreyi aşan
çevrimiçi cihazları offline’a çevirir.
Komut kuyruğu
Yönetici, bir cihaza komut göndererek donanımı sürücüler. Komutlar cihaz-başına
FIFO + öncelikli bir kuyrukta tutulur; cihaz next-command ile sıradaki komutu
çeker, çalıştırır ve ack ile sonuç bildirir.
Komut türleri (kind)
kind | Donanım eylemi |
|---|---|
print_receipt | Müşteri fişi yazdır |
open_drawer | Nakit çekmeceyi aç |
fiscal_receipt | Yazarkasa mali fişi kes |
fiscal_cancel | Mali fişi iptal et (fiscal void) |
charge_card | Kart terminalinden tahsilat başlat |
show_order / clear_order | Ekranda sipariş göster / temizle |
reboot / firmware_update | Cihazı yeniden başlat / firmware güncelle |
capability_probe / noop | Yetenek yokla / boş komut |
Komut kuyruğa alma (admin)
curl -X POST https://hummytummy.com/api/v1/devices/$DEVICE_ID/commands \
-H "Authorization: Bearer $JWT" \
-H "X-Branch-Id: $BRANCH_ID" \
-H "Content-Type: application/json" \
-d '{
"kind": "print_receipt",
"payload": { "orderId": "01J...", "copies": 1 },
"priority": 0,
"idempotencyKey": "order-01J-receipt"
}'kindyukarıdaki kapalı kümeden olmalıdır; serbest takma adlar (charge.cardgibi)400ile reddedilir.payloadbir nesne (JSONB) olmalıdır;priority0–1000 arası (0 varsayılan, yüksek = önce).idempotencyKeyverilirse(deviceId, idempotencyKey)üzerinde tekilleştirilir; tekrar gönderim aynı komutu döndürür (çift komut yaratmaz). Yeniden denerken mutlaka sabit bir anahtar gönderin.
Cihaz tarafı: komut çekme ve ack
# Sıradaki komutu atomik olarak çek (yoksa null döner)
curl https://hummytummy.com/api/v1/devices/next-command \
-H "Authorization: Device $DEVICE_TOKEN"
# Sonucu bildir
curl -X POST https://hummytummy.com/api/v1/devices/commands/$COMMAND_ID/ack \
-H "Authorization: Device $DEVICE_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "status": "done", "result": { "bytesPrinted": 412 } }'next-command, FOR UPDATE SKIP LOCKED ile atomik çalışır — aynı cihaz iki
bağlantı açsa bile komut iki kez teslim edilmez. ack durumu done ya da
failed olabilir; error alanı (≤ 1000 karakter) başarısızlık nedenini taşır.
Yeniden deneme semantiği — idempotency ve yan etkiler
Para taşıyan / kalıcı çıktı üreten komutlar asla otomatik yeniden denenmez.
charge_card, fiscal_receipt, fiscal_cancel, open_drawer ve
print_receipt yan etkilidir: ack kaybolan bir komut (terminal tahsilatı
yaptı → uygulama çöktü → sonuç sunucuya ulaşmadı) tekrar kuyruğa konulursa
müşteriyi çift çeker ya da fişi çift basar. Bu nedenle bu komutlar
başarısızlıkta doğrudan failed durumunda sonlanır; operatör açık bir telafi
komutu (örn. fiscal_cancel) ile uzlaştırır. Güvenli/idempotent komutlar
(show_order, clear_order, capability_probe, reboot, noop) ise normal
yeniden deneme yolunu izler (en çok 5 deneme).
Ekran komutları (KDS / bar ekranları)
kind: "kds_screen" (veya bar_screen) ile kaydedilmiş bir KDS/bar ekranı,
komut kümesinin güvenli ekran alt kümesiyle sürücülenebilir:
kind | Eylem |
|---|---|
show_order | Ekranda bir siparişi göster |
clear_order | Gösterilen siparişi temizle |
reboot | Ekran cihazını yeniden başlat |
capability_probe | Yetenekleri yokla |
noop | Boş komut (bağlantı testi) |
curl -X POST https://hummytummy.com/api/v1/devices/$SCREEN_ID/commands \
-H "Authorization: Bearer $JWT" \
-H "X-Branch-Id: $BRANCH_ID" \
-H "Content-Type: application/json" \
-d '{ "kind": "show_order", "payload": { "orderId": "01J..." } }'Bunların tümü idempotent / güvenlidir — yan etkili donanım komutlarının aksine ekranı çift güncellemek zarar vermez; bu yüzden başarısızlıkta normal yeniden deneme yolunu izlerler (en çok 5 deneme).
Cihazları listeleme
curl "https://hummytummy.com/api/v1/devices?branchId=$BRANCH_ID&kind=receipt_printer&status=online" \
-H "Authorization: Bearer $JWT" \
-H "X-Branch-Id: $BRANCH_ID"branchId, kind ve status ile filtrelenebilir. Liste yanıtı kimlik + durum
bilgisini döndürür; pairCode ve tokenHash gibi hassas alanlar bilinçli olarak
listeden çıkarılmıştır.
Hız sınırları
API Temelleri sayfasındaki global katmanlara ek olarak cihaz uç noktaları kendi sıkı sınırlarını ekler:
| Uç nokta | Sınır |
|---|---|
POST /api/v1/devices/pair | 5 / dk / IP |
POST /api/v1/devices/heartbeat | 60 / dk |
GET /api/v1/devices/next-command | 120 / dk |
POST /api/v1/devices/commands/:id/ack | 120 / dk |
heartbeat, next-command ve ack üzerindeki token-başına sınırlar, ele
geçmiş bir cihaz token’ının veritabanını yormasını engeller.
Güvenlik notları
- Komut gönderme ve kuyruğu görüntüleme şube kapsamına bağlıdır: tek şubeye
kısıtlı bir
MANAGER, başka şubenin terminalinecharge_card/open_drawer/fiscal_receiptgönderemez (kapsam dışı cihaz404döner).ADMINkiracı genelinde çalışır. - Cihazı emekliye ayırma (
DELETE /api/v1/devices/:id) yalnızcaADMINrolüne açıktır; token’ı iptal eder. - Ham cihaz token’ı eşleşme anında bir kez gösterilir ve yalnızca sha256 hash’i olarak saklanır; kaybolursa yuvayı emekliye ayırıp yeniden eşleştirin.
Masaüstü uygulamasının yerel Bluetooth/ESC/POS desteği (scan_devices,
connect_device, print_receipt gibi Tauri komutları), istemci içinde çalışan
istemci tarafı BLE yazdırmadır. Buradaki device-mesh ise sunucu tarafı
kayıt, eşleştirme ve komut kuyruğudur. İkisi birbirini tamamlar: bir
local_bridge, mesh’ten aldığı print_receipt komutunu yerel ESC/POS yazıcı
işine dönüştürebilir.
Operatör ekranları
Restoran personeli cihazları doğrudan API’den değil, uygulama içi panelden yönetir:
- Cihazlar ekranı — yuva oluşturma, pair code/QR okuma, durum izleme, yetenek yoklaması gönderme, emekliye ayırma.
- Donanım eşleştirme — yazıcı, nakit çekmece ve yazarkasa için operatör tarafı eşleştirme anlatımı.
- KDS / kiosk modu — bir mutfak ekranını kayıtlı ekran cihazı olarak çalıştırma.