Geliştirici / APICihaz API (Device Mesh)

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.

RealmBaşlıkKim kullanırNe için
Personel JWTAuthorization: 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_bridgeEş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:

kindAçıklama
receipt_printerFiş yazıcısı
kitchen_printerMutfak yazıcısı
yazarkasaYazarkasa (mali / fiscal cihaz)
pos_terminalKart ödeme terminali
kds_screen / bar_screenMutfak / bar ekranı
tablet_waiter / tablet_customerGarson / müşteri tableti
caller_idÇağrı kimliği cihazı
scannerBarkod okuyucu
local_bridgeBirden ç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

MetotYolRealmNotlar
POST/api/v1/devicesPersonel JWTCihaz yuvası oluştur; pair code döner
GET/api/v1/devicesPersonel JWTCihazları listele (branchId, kind, status ile filtre)
DELETE/api/v1/devices/:idPersonel JWT (ADMIN)Cihazı emekliye ayır; token’ı iptal eder
POST/api/v1/devices/:id/commandsPersonel JWTCihaza komut kuyruğa al
POST/api/v1/devices/pairCihaz (pair code)Yuvayı pair code ile sahiplen; ham token’ı bir kez döner
POST/api/v1/devices/heartbeatCihaz token’ıKeep-alive + telemetri
GET/api/v1/devices/next-commandCihaz token’ıSıradaki komutu atomik olarak çek
POST/api/v1/devices/commands/:id/ackCihaz 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.
  • /pair uç 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)

kindDonanım eylemi
print_receiptMüşteri fişi yazdır
open_drawerNakit çekmeceyi aç
fiscal_receiptYazarkasa mali fişi kes
fiscal_cancelMali fişi iptal et (fiscal void)
charge_cardKart terminalinden tahsilat başlat
show_order / clear_orderEkranda sipariş göster / temizle
reboot / firmware_updateCihazı yeniden başlat / firmware güncelle
capability_probe / noopYetenek 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"
  }'
  • kind yukarıdaki kapalı kümeden olmalıdır; serbest takma adlar (charge.card gibi) 400 ile reddedilir.
  • payload bir nesne (JSONB) olmalıdır; priority 0–1000 arası (0 varsayılan, yüksek = önce).
  • idempotencyKey verilirse (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:

kindEylem
show_orderEkranda bir siparişi göster
clear_orderGösterilen siparişi temizle
rebootEkran cihazını yeniden başlat
capability_probeYetenekleri yokla
noopBoş 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ç noktaSınır
POST /api/v1/devices/pair5 / dk / IP
POST /api/v1/devices/heartbeat60 / dk
GET /api/v1/devices/next-command120 / dk
POST /api/v1/devices/commands/:id/ack120 / 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 terminaline charge_card / open_drawer / fiscal_receipt gönderemez (kapsam dışı cihaz 404 döner). ADMIN kiracı genelinde çalışır.
  • Cihazı emekliye ayırma (DELETE /api/v1/devices/:id) yalnızca ADMIN rolü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.