diff --git a/Documentation/NiimbotPrinter-FlowChart.md b/Documentation/NiimbotPrinter-FlowChart.md new file mode 100644 index 0000000..cee9a9b --- /dev/null +++ b/Documentation/NiimbotPrinter-FlowChart.md @@ -0,0 +1,717 @@ +# πŸ“Š Diagram Alur Kerja Printer Niimbot + +## πŸ”„ Alur Lengkap: Dari UI ke Printer + +```mermaid +graph TD + A[User buka halaman Brankas] --> B{Printer sudah
terhubung?} + B -->|Tidak| C[Klik 'Hubungkan Printer'] + B -->|Ya| D[Status: Terhubung] + + C --> E[Modal NiimbotConnector muncul] + E --> F{Pilih metode koneksi} + F -->|Bluetooth| G[Klik 'Hubungkan Printer'] + F -->|USB/Serial| G + + G --> H[Browser: Dialog pairing muncul] + H --> I[User pilih Niimbot device] + I --> J[Library: initClient & connect] + + J --> K{Koneksi
berhasil?} + K -->|Tidak| L[Tampilkan error] + K -->|Ya| M[Fetch printer info] + + M --> N[Status: Terhubung βœ“] + N --> D + + D --> O[User klik item di Brankas] + O --> P[Modal item muncul] + P --> Q[Generate QR Code URL] + Q --> R[Tampilkan preview QR] + + R --> S[User klik 'Cetak ke Niimbot'] + S --> T{Printer
terhubung?} + + T -->|Tidak| U[Alert: Hubungkan printer] + U --> C + + T -->|Ya| V[createQRLabelCanvas] + V --> W[Load QR image] + W --> X[Draw ke canvas:
QR + Kode + Nama + Berat] + X --> Y[Convert canvas to DataURL] + + Y --> Z[printQRCode composable] + Z --> AA[Stop heartbeat] + AA --> AB[Create PrintTask] + AB --> AC[ImageEncoder.encodeCanvas] + AC --> AD[printTask.printInit] + AD --> AE[printTask.printPage] + + AE --> AF{Print
sukses?} + AF -->|Tidak| AG[Tampilkan error] + AF -->|Ya| AH[printTask.printEnd] + + AH --> AI[Start heartbeat] + AI --> AJ[Alert: Berhasil dicetak!] + AJ --> AK[Printer cetak label] +``` + +--- + +## πŸ—οΈ Arsitektur Komponen + +``` +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ BrankasList.vue β”‚ +β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ +β”‚ β”‚ UI Layer β”‚ β”‚ +β”‚ β”‚ - Tombol "Hubungkan Printer" β”‚ β”‚ +β”‚ β”‚ - Tombol "Cetak ke Niimbot" β”‚ β”‚ +β”‚ β”‚ - Modal item dengan QR Code β”‚ β”‚ +β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ +β”‚ β”‚ β”‚ +β”‚ β–Ό β”‚ +β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ +β”‚ β”‚ Logic Layer β”‚ β”‚ +β”‚ β”‚ - printQR() β†’ Trigger print β”‚ β”‚ +β”‚ β”‚ - createQRLabelCanvas() β†’ Generate canvas dengan QR β”‚ β”‚ +β”‚ β”‚ - handlePrinterConnected() β†’ Event handler β”‚ β”‚ +β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + β”‚ + β–Ό +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ useNiimbotPrinter.js (Composable) β”‚ +β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ +β”‚ β”‚ State Management β”‚ β”‚ +β”‚ β”‚ - printerClient (reactive) β”‚ β”‚ +β”‚ β”‚ - connectionState β”‚ β”‚ +β”‚ β”‚ - isPrinting, printProgress β”‚ β”‚ +β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ +β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ +β”‚ β”‚ Methods β”‚ β”‚ +β”‚ β”‚ - initClient() β†’ Buat NiimbotClient β”‚ β”‚ +β”‚ β”‚ - connect() β†’ Hubungkan ke printer β”‚ β”‚ +β”‚ β”‚ - disconnect() β†’ Putuskan koneksi β”‚ β”‚ +β”‚ β”‚ - printQRCode() β†’ Print image ke printer β”‚ β”‚ +β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + β”‚ + β–Ό +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ @mmote/niimbluelib (Library) β”‚ +β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ +β”‚ β”‚ NiimbotBluetoothClient / NiimbotSerialClient β”‚ β”‚ +β”‚ β”‚ - connect() β†’ Web Bluetooth/Serial API β”‚ β”‚ +β”‚ β”‚ - fetchPrinterInfo() β†’ Get printer metadata β”‚ β”‚ +β”‚ β”‚ - startHeartbeat() β†’ Maintain connection β”‚ β”‚ +β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ +β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ +β”‚ β”‚ PrintTask β”‚ β”‚ +β”‚ β”‚ - newPrintTask() β†’ Buat task print β”‚ β”‚ +β”‚ β”‚ - printInit() β†’ Inisialisasi print β”‚ β”‚ +β”‚ β”‚ - printPage() β†’ Kirim data gambar β”‚ β”‚ +β”‚ β”‚ - waitForFinished() β†’ Tunggu selesai β”‚ β”‚ +β”‚ β”‚ - printEnd() β†’ Akhiri print β”‚ β”‚ +β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ +β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ +β”‚ β”‚ ImageEncoder β”‚ β”‚ +β”‚ β”‚ - encodeCanvas() β†’ Encode canvas ke binary β”‚ β”‚ +β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + β”‚ + β–Ό +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Browser Web APIs β”‚ +β”‚ - Web Bluetooth API (navigator.bluetooth) β”‚ +β”‚ - Web Serial API (navigator.serial) β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + β”‚ + β–Ό +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Printer Niimbot (Hardware) β”‚ +β”‚ - Terima perintah via BLE/USB β”‚ +β”‚ - Decode binary image data β”‚ +β”‚ - Print ke label thermal β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +``` + +--- + +## πŸ“‘ Sequence Diagram: Proses Print + +``` +User BrankasList useNiimbot niimbluelib Browser API Printer + β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ + β”‚ Klik item β”‚ β”‚ β”‚ β”‚ β”‚ + β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€>β”‚ β”‚ β”‚ β”‚ β”‚ + β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ + β”‚ Modal muncul β”‚ β”‚ β”‚ β”‚ β”‚ + β”‚<──────────────── β”‚ β”‚ β”‚ β”‚ + β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ + β”‚ Klik 'Cetak' β”‚ β”‚ β”‚ β”‚ β”‚ + β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€>β”‚ β”‚ β”‚ β”‚ β”‚ + β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ + β”‚ β”‚ printQRCode()β”‚ β”‚ β”‚ β”‚ + β”‚ β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€>β”‚ β”‚ β”‚ β”‚ + β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ + β”‚ β”‚ β”‚ stopHeartbeat() β”‚ β”‚ + β”‚ β”‚ β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€>β”‚ β”‚ β”‚ + β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ + β”‚ β”‚ β”‚ newPrintTask() β”‚ β”‚ + β”‚ β”‚ β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€>β”‚ β”‚ β”‚ + β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ + β”‚ β”‚ β”‚ encodeCanvas() β”‚ β”‚ + β”‚ β”‚ β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€>β”‚ β”‚ β”‚ + β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ + β”‚ β”‚ β”‚ β”‚ BLE/Serial β”‚ β”‚ + β”‚ β”‚ β”‚ β”‚ Write Data β”‚ β”‚ + β”‚ β”‚ β”‚ β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€>β”‚ β”‚ + β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ + β”‚ β”‚ β”‚ β”‚ β”‚ Send Data β”‚ + β”‚ β”‚ β”‚ β”‚ β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€>β”‚ + β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ + β”‚ β”‚ β”‚ β”‚ β”‚ Printing β”‚ + β”‚ β”‚ β”‚ β”‚ β”‚ ........ β”‚ + β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ + β”‚ β”‚ β”‚ β”‚ Status ACK β”‚ β”‚ + β”‚ β”‚ β”‚ β”‚<────────────── β”‚ + β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ + β”‚ β”‚ β”‚ printEnd() β”‚ β”‚ β”‚ + β”‚ β”‚ β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€>β”‚ β”‚ β”‚ + β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ + β”‚ β”‚ β”‚ startHeartbeat() β”‚ β”‚ + β”‚ β”‚ β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€>β”‚ β”‚ β”‚ + β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ + β”‚ β”‚ Alert sukses β”‚ β”‚ β”‚ β”‚ + β”‚ β”‚<────────────── β”‚ β”‚ β”‚ + β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ + β”‚ Label tercetakβ”‚ β”‚ β”‚ β”‚ β”‚ + β”‚<───────────────┴──────────────┴──────────────┴──────────────┴───────────── + β”‚ β”‚ +``` + +--- + +## πŸ”Œ Connection Flow Detail + +### Bluetooth Connection +``` +1. User Action + └─> Klik "Hubungkan Printer" + +2. initClient('bluetooth') + └─> new NiimbotBluetoothClient() + +3. connect() + └─> navigator.bluetooth.requestDevice({ + filters: [{ namePrefix: 'Niimbot' }], + optionalServices: [SERVICE_UUID] + }) + +4. Browser Dialog + └─> User pilih device "Niimbot-XXXX" + +5. device.gatt.connect() + └─> Establish BLE connection + +6. getPrimaryService(SERVICE_UUID) + └─> getCharacteristic(TX_CHAR, RX_CHAR) + +7. Event: 'connect' triggered + └─> connectionState = 'connected' + +8. fetchPrinterInfo() + └─> Send command: GET_INFO + └─> Receive: model, serial, firmware version + +9. startHeartbeat() + └─> Kirim ping setiap 1 detik + └─> Cek printer masih hidup +``` + +### USB/Serial Connection +``` +1. User Action + └─> Klik "Hubungkan Printer" + +2. initClient('serial') + └─> new NiimbotSerialClient() + +3. connect() + └─> navigator.serial.requestPort({ + filters: [{ usbVendorId: 0xXXXX }] + }) + +4. Browser Dialog + └─> User pilih USB device + +5. port.open({ baudRate: 9600 }) + └─> Establish serial connection + +6. Setup reader/writer streams + └─> readable.getReader() + └─> writable.getWriter() + +7. Event: 'connect' triggered + └─> connectionState = 'connected' + +8. fetchPrinterInfo() & startHeartbeat() + └─> (sama dengan Bluetooth) +``` + +--- + +## πŸ–ΌοΈ Image Processing Pipeline + +``` +QR Code URL (dari API) + β”‚ + β–Ό + new Image() + β”‚ + β–Ό + img.onload + β”‚ + β–Ό +Create Canvas (384x384) + β”‚ + β”œβ”€> Fill white background + β”‚ + β”œβ”€> drawImage(qr, x, y, size, size) + β”‚ + β”œβ”€> fillText(kode_item) + β”‚ + β”œβ”€> fillText(nama_produk) + β”‚ + β”œβ”€> fillText(berat) + β”‚ + β–Ό +canvas.toDataURL('image/png') + β”‚ + β–Ό +loadImageToCanvas(dataUrl) + β”‚ + β–Ό +[Optional] applyThreshold(canvas, 140) + β”‚ + β”œβ”€> getImageData() + β”‚ + β”œβ”€> for each pixel: + β”‚ avg = (r+g+b)/3 + β”‚ if avg < 140: black + β”‚ else: white + β”‚ + β”œβ”€> putImageData() + β”‚ + β–Ό +ImageEncoder.encodeCanvas(canvas, 'top') + β”‚ + β”œβ”€> Convert to 1-bit bitmap + β”‚ + β”œβ”€> Rotate if needed (printDirection) + β”‚ + β”œβ”€> Pack bits: 8 pixels = 1 byte + β”‚ + β–Ό +Binary Data (EncodedImage) + β”‚ + β–Ό +printTask.printPage(encoded, quantity) + β”‚ + β–Ό +Send to Printer via BLE/Serial +``` + +--- + +## πŸ“Š State Machine Diagram + +``` +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ DISCONNECTED │◄────────┐ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ + β”‚ β”‚ + β”‚ connect() β”‚ disconnect() + β”‚ β”‚ + β–Ό β”‚ +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ +β”‚ CONNECTING β”‚ β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ + β”‚ β”‚ + β”‚ success β”‚ error + β”‚ β”‚ + β–Ό β”‚ +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ +β”‚ CONNECTED │────────── +β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ + β”‚ β”‚ + β”‚ printQRCode() β”‚ + β”‚ β”‚ + β–Ό β”‚ +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ +β”‚ PRINTING β”‚ β”‚ +β”‚ [progress: X%] β”‚ β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ + β”‚ β”‚ + β”‚ finished β”‚ + β”‚ β”‚ + β–Ό β”‚ +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ +β”‚ CONNECTED β”‚β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +β”‚ (heartbeat) β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +``` + +--- + +## 🎯 Data Flow: QR Code β†’ Printed Label + +``` +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ 1. DATA SOURCE β”‚ +β”‚ ─────────────────────────────────────────────────────────│ +β”‚ selectedItem = { β”‚ +β”‚ kode_item: "BRN-001", β”‚ +β”‚ produk: { β”‚ +β”‚ nama: "Cincin Emas 24K", β”‚ +β”‚ berat: 5.2 β”‚ +β”‚ } β”‚ +β”‚ } β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + β”‚ + β–Ό +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ 2. QR CODE GENERATION (External API) β”‚ +β”‚ ─────────────────────────────────────────────────────────│ +β”‚ URL: https://api.qrserver.com/v1/create-qr-code/ β”‚ +β”‚ ?size=150x150&data=BRN-001 β”‚ +β”‚ β”‚ +β”‚ Returns: image/png (base64 or URL) β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + β”‚ + β–Ό +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ 3. CANVAS CREATION β”‚ +β”‚ ─────────────────────────────────────────────────────────│ +β”‚ createQRLabelCanvas(qrUrl, item) β”‚ +β”‚ β”‚ +β”‚ Canvas 384x384px: β”‚ +β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ +β”‚ β”‚ β”‚ β”‚ +β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ ← QR Code (200x200) β”‚ +β”‚ β”‚ β”‚ β–“β–“ β–“β–“β–“β–“β”‚ β”‚ β”‚ +β”‚ β”‚ β”‚β–“ β–“β–“ β–“ β”‚ β”‚ β”‚ +β”‚ β”‚ β”‚ β–“β–“β–“ β–“β–“β–“β”‚ β”‚ β”‚ +β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ +β”‚ β”‚ β”‚ β”‚ +β”‚ β”‚ BRN-001 β”‚ ← Kode Item (bold 18px) β”‚ +β”‚ β”‚ Cincin Emas 24K β”‚ ← Nama Produk (14px) β”‚ +β”‚ β”‚ 5.2g β”‚ ← Berat (12px) β”‚ +β”‚ β”‚ β”‚ β”‚ +β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + β”‚ + β–Ό +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ 4. IMAGE ENCODING β”‚ +β”‚ ─────────────────────────────────────────────────────────│ +β”‚ ImageEncoder.encodeCanvas(canvas, 'top') β”‚ +β”‚ β”‚ +β”‚ Process: β”‚ +β”‚ - Convert RGB to grayscale β”‚ +β”‚ - Apply threshold (< 140 = black, >= 140 = white) β”‚ +β”‚ - Pack 8 pixels into 1 byte (1-bit bitmap) β”‚ +β”‚ - Width: 384px = 48 bytes per row β”‚ +β”‚ - Height: 384 rows β”‚ +β”‚ - Total: 48 Γ— 384 = 18,432 bytes β”‚ +β”‚ β”‚ +β”‚ Output: Uint8Array(18432) β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + β”‚ + β–Ό +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ 5. PRINT PROTOCOL β”‚ +β”‚ ─────────────────────────────────────────────────────────│ +β”‚ printTask.printInit() β”‚ +β”‚ └─> Send: [CMD_INIT, density, labelType, ...] β”‚ +β”‚ β”‚ +β”‚ printTask.printPage(encoded, quantity) β”‚ +β”‚ └─> Send in chunks: β”‚ +β”‚ [CMD_IMAGE_HEADER, width, height] β”‚ +β”‚ [CMD_IMAGE_DATA, chunk1...] β”‚ +β”‚ [CMD_IMAGE_DATA, chunk2...] β”‚ +β”‚ ... β”‚ +β”‚ [CMD_IMAGE_END] β”‚ +β”‚ β”‚ +β”‚ printTask.waitForFinished() β”‚ +β”‚ └─> Poll status every 100ms β”‚ +β”‚ until status = FINISHED β”‚ +β”‚ β”‚ +β”‚ printTask.printEnd() β”‚ +β”‚ └─> Send: [CMD_PRINT_END] β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + β”‚ + β–Ό +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ 6. PHYSICAL OUTPUT β”‚ +β”‚ ─────────────────────────────────────────────────────────│ +β”‚ Printer Niimbot: β”‚ +β”‚ - Receives binary data via BLE/USB β”‚ +β”‚ - Thermal head heats selected pixels β”‚ +β”‚ - Paper feeds forward β”‚ +β”‚ - Label printed with QR + text β”‚ +β”‚ - Cut (automatic or manual) β”‚ +β”‚ β”‚ +β”‚ Result: Physical label 40x40mm dengan QR code β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +``` + +--- + +## 🧩 Component Interaction Map + +``` + β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” + β”‚ App.vue / Router β”‚ + β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + β”‚ + β–Ό + β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” + β”‚ BrankasList.vue β”‚ + β”‚ β”‚ + β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ + β”‚ β”‚ Header β”‚ β”‚ + β”‚ β”‚ - Printer Btn │──┼──┐ + β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ + β”‚ β”‚ β”‚ + β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ + β”‚ β”‚ Item List β”‚ β”‚ β”‚ + β”‚ β”‚ - Click item │──┼──┼──┐ + β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ β”‚ + β”‚ β”‚ β”‚ β”‚ + β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ β”‚ + β”‚ β”‚ Item Modal β”‚ β”‚ β”‚ β”‚ + β”‚ β”‚ - QR Preview β”‚ β”‚ β”‚ β”‚ + β”‚ β”‚ - Print Btn │──┼──┼──┼──┐ + β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ β”‚ β”‚ + β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ β”‚ + β”‚ β”‚ β”‚ + β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ + β”‚ β”‚ β”‚ + β–Ό β”‚ β”‚ +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ +β”‚ NiimbotConnector β”‚ β”‚ β”‚ +β”‚ .vue β”‚ β”‚ β”‚ +β”‚ β”‚ β”‚ β”‚ +β”‚ - Connect UI β”‚β—„β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ +β”‚ - Status display β”‚ β”‚ +β”‚ - Printer info β”‚ β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ + β”‚ β”‚ + β”‚ uses β”‚ + β”‚ β”‚ + β–Ό β”‚ +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β” +β”‚ useNiimbotPrinter.js (Composable) β”‚ β”‚ +β”‚ β”‚ β”‚ +β”‚ - State: printerClient, connectionState, etc β”‚β—„β”€β”€β”˜ +β”‚ - Methods: connect(), disconnect(), printQR() β”‚ +β”‚ β”‚ +β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ +β”‚ β”‚ Event Listeners Setup β”‚ β”‚ +β”‚ β”‚ - on('connect') β”‚ β”‚ +β”‚ β”‚ - on('disconnect') β”‚ β”‚ +β”‚ β”‚ - on('printprogress') β”‚ β”‚ +β”‚ β”‚ - on('heartbeat') β”‚ β”‚ +β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + β”‚ imports + β–Ό +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ @mmote/niimbluelib β”‚ +β”‚ β”‚ +β”‚ - NiimbotBluetoothClient β”‚ +β”‚ - NiimbotSerialClient β”‚ +β”‚ - ImageEncoder β”‚ +β”‚ - PrintTask abstraction β”‚ +β”‚ - Utils β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + β”‚ uses + β–Ό +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Browser APIs β”‚ +β”‚ - navigator.bluetooth (Web Bluetooth API) β”‚ +β”‚ - navigator.serial (Web Serial API) β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +``` + +--- + +## ⚑ Performance Timeline + +``` +Event Timeline (typical print job): + +0ms β”‚ User clicks "Cetak ke Niimbot" + β”‚ +10ms β”‚ Check printer connection + β”‚ └─> Already connected βœ“ + β”‚ +20ms β”‚ createQRLabelCanvas() start + β”‚ └─> Load QR image + β”‚ +150ms β”‚ Image loaded, draw to canvas + β”‚ └─> Draw QR, text, etc + β”‚ +170ms β”‚ canvas.toDataURL() + β”‚ +200ms β”‚ printQRCode() called + β”‚ └─> stopHeartbeat() + β”‚ +220ms β”‚ newPrintTask() + β”‚ +240ms β”‚ ImageEncoder.encodeCanvas() + β”‚ └─> RGB β†’ grayscale + β”‚ └─> Threshold + β”‚ └─> Pack to 1-bit + β”‚ +450ms β”‚ Encoding complete (18KB data) + β”‚ +470ms β”‚ printTask.printInit() + β”‚ └─> Send init command + β”‚ +490ms β”‚ ACK received + β”‚ +500ms β”‚ printTask.printPage() + β”‚ └─> Send image header + β”‚ +520ms β”‚ Send data chunk 1/10 +540ms β”‚ Send data chunk 2/10 +... β”‚ ... +740ms β”‚ Send data chunk 10/10 + β”‚ +750ms β”‚ All data sent + β”‚ +780ms β”‚ waitForFinished() polling start + β”‚ +800ms β”‚ Status: PRINTING (0%) +900ms β”‚ Status: PRINTING (25%) +1000ms β”‚ Status: PRINTING (50%) +1100ms β”‚ Status: PRINTING (75%) +1200ms β”‚ Status: PRINTING (100%) + β”‚ +1220ms β”‚ Status: FINISHED + β”‚ +1230ms β”‚ printTask.printEnd() + β”‚ +1250ms β”‚ startHeartbeat() + β”‚ +1260ms β”‚ Alert: "QR Code berhasil dicetak!" + β”‚ +Total: ~1.3 seconds dari klik hingga selesai +``` + +--- + +## πŸ” Security & Privacy + +``` +Data Flow Security: + +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ User's Browser β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + β”‚ + β”‚ HTTPS (encrypted) + β”‚ + β–Ό +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ QR API Server β”‚ ← External: api.qrserver.com +β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ (Kirim kode_item only) + β”‚ + β”‚ Returns image URL + β”‚ + β–Ό +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ User's Browser β”‚ +β”‚ - Generate β”‚ +β”‚ canvas β”‚ +β”‚ - Encode image β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + β”‚ + β”‚ BLE/USB (direct, tidak via internet) + β”‚ Encrypted jika BLE + β”‚ + β–Ό +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Niimbot Printer β”‚ ← Local device, tidak terkoneksi internet +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + +Privacy Notes: +- Data tidak lewat server backend aplikasi +- QR Code dibuat real-time di browser +- Gambar langsung dikirim ke printer lokal +- Tidak ada logging data item ke cloud +``` + +--- + +## πŸ“ˆ Error Handling Flow + +``` + β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” + β”‚ User Action β”‚ + β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + β”‚ + β–Ό + β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” + β”‚ Try: connect() β”‚ + β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + β”‚ + β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” + β”‚ β”‚ + β”Œβ”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β” + β”‚ Success β”‚ β”‚ Error β”‚ + β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”˜ + β”‚ β”‚ + β”‚ β–Ό + β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” + β”‚ β”‚ Catch error β”‚ + β”‚ β”‚ - Log to β”‚ + β”‚ β”‚ console β”‚ + β”‚ β”‚ - Set error β”‚ + β”‚ β”‚ message β”‚ + β”‚ β”‚ - Alert user β”‚ + β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”˜ + β”‚ β”‚ + β”‚ β–Ό + β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” + β”‚ β”‚ connectionStateβ”‚ + β”‚ β”‚ = 'disconnectedβ”‚ + β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + β”‚ + β–Ό + β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” + β”‚ fetchPrinterInfoβ”‚ + β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + β”‚ + β”Œβ”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β” + β”‚ β”‚ + β”Œβ”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β–Όβ”€β”€β”€β”€β” + β”‚ Success β”‚ β”‚ Error β”‚ + β””β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”˜ + β”‚ β”‚ + β”‚ └──> Log & continue (non-critical) + β”‚ + β–Ό +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ startHeartbeat()β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”˜ + β”‚ + └──> Connected & Ready +``` + +--- + +Semua diagram ini membantu memahami bagaimana sistem bekerja end-to-end! πŸŽ‰ diff --git a/Documentation/NiimbotPrinter.md b/Documentation/NiimbotPrinter.md new file mode 100644 index 0000000..8c55f36 --- /dev/null +++ b/Documentation/NiimbotPrinter.md @@ -0,0 +1,373 @@ +# πŸ–¨οΈ Dokumentasi Integrasi Printer Niimbot + +## πŸ“‹ Ringkasan + +Aplikasi kasir sekarang mendukung pencetakan QR Code langsung ke printer Niimbot menggunakan library `@mmote/niimbluelib` yang diadaptasi dari proyek niimblue. + +--- + +## πŸ”§ Teknologi & Library + +### Library Utama +- **@mmote/niimbluelib** - Library JavaScript untuk komunikasi dengan printer Niimbot + - Mendukung Web Bluetooth API + - Mendukung Web Serial API (USB) + - Encoding gambar ke format printer + +### Browser Support +| Browser | Bluetooth | USB (Serial) | +|---------|-----------|--------------| +| Chrome/Edge (Desktop) | βœ… | βœ… | +| Chrome (Android) | βœ… | ❌ | +| Firefox | ❌ | ❌ | +| Safari | ❌ | ❌ | + +**Rekomendasi:** Gunakan Chrome atau Edge versi terbaru + +--- + +## πŸ“ Struktur File + +``` +Kasir/resources/js/ +β”œβ”€β”€ composables/ +β”‚ └── useNiimbotPrinter.js # Composable untuk koneksi & print +β”œβ”€β”€ components/ +β”‚ β”œβ”€β”€ NiimbotConnector.vue # Modal untuk koneksi printer +β”‚ └── BrankasList.vue # Modifikasi: tambah fitur print Niimbot +``` + +--- + +## πŸš€ Cara Kerja + +### 1️⃣ Alur Koneksi Printer + +``` +User klik "Hubungkan Printer" + β†’ Modal NiimbotConnector muncul + β†’ Pilih metode: Bluetooth / USB + β†’ Browser munculkan dialog pairing + β†’ User pilih printer Niimbot + β†’ Koneksi terjalin + β†’ Fetch info printer + β†’ Status: "Terhubung" +``` + +### 2️⃣ Alur Print QR Code + +``` +User klik item di Brankas + β†’ Modal popup dengan QR Code + β†’ User klik "Cetak ke Niimbot" + β†’ Cek koneksi printer: + - Jika belum terhubung β†’ tampilkan modal koneksi + - Jika sudah terhubung β†’ lanjut + β†’ Generate canvas dengan QR + info item + β†’ Encode canvas ke format printer + β†’ Kirim ke printer via library + β†’ Status: "Mencetak... X%" + β†’ Selesai +``` + +### 3️⃣ Komponen Kunci + +#### `useNiimbotPrinter.js` (Composable) +```javascript +// State management +- printerClient // Instance NiimbotClient +- connectionState // 'disconnected' | 'connecting' | 'connected' +- connectedPrinterName // Nama printer yang terhubung +- printerInfo // Info printer (model, serial, dll) +- isPrinting // Status sedang print +- printProgress // Progress print (0-100) + +// Methods +- initClient(type) // Init client (bluetooth/serial) +- connect() // Hubungkan ke printer +- disconnect() // Putuskan koneksi +- printQRCode(dataUrl, options) // Print QR code image +``` + +#### `NiimbotConnector.vue` (Component) +- Modal untuk koneksi/disconnect printer +- Pilih metode: Bluetooth atau USB +- Tampilkan status koneksi +- Tampilkan info printer detail + +#### `BrankasList.vue` (Modified) +- Tombol "Hubungkan Printer" di header +- Tombol "Cetak ke Niimbot" di modal item +- Tombol "Browser" sebagai fallback +- Alert sukses/error + +--- + +## πŸ› οΈ Instalasi + +### 1. Install Library +```bash +cd "c:\Data\Magang\Toko perhiasan\Kasir" +npm install @mmote/niimbluelib +``` + +### 2. Files Sudah Dibuat +- βœ… `resources/js/composables/useNiimbotPrinter.js` +- βœ… `resources/js/components/NiimbotConnector.vue` +- βœ… `resources/js/components/BrankasList.vue` (modified) + +### 3. Build Assets +```bash +npm run build +# atau untuk development +npm run dev +``` + +--- + +## πŸ“– Cara Penggunaan + +### A. Menghubungkan Printer + +1. **Persiapan Printer:** + - Nyalakan printer Niimbot + - Pastikan kertas label sudah terpasang + - Untuk Bluetooth: Aktifkan mode pairing (biasanya tahan tombol power) + +2. **Di Aplikasi:** + - Buka halaman Brankas + - Klik tombol "Hubungkan Printer" (di kanan atas) + - Pilih metode koneksi (Bluetooth/USB) + - Klik "Hubungkan Printer" + - Browser akan menampilkan dialog - pilih printer Niimbot Anda + - Tunggu hingga status berubah "Terhubung" + +### B. Mencetak QR Code + +1. **Pilih Item:** + - Klik item yang ingin dicetak QR-nya + - Modal akan muncul dengan QR Code + +2. **Cetak:** + - Klik tombol "Cetak ke Niimbot" + - Jika printer belum terhubung, akan muncul peringatan + - Progress print akan ditampilkan (Mencetak... X%) + - Tunggu hingga selesai + +3. **Alternatif:** + - Klik tombol "Browser" untuk print menggunakan printer biasa + +--- + +## βš™οΈ Konfigurasi Print + +Di file `BrankasList.vue`, fungsi `printQR()`: + +```javascript +await printQRCode(imageDataUrl, { + density: 3, // Kepadatan tinta (1-5, default: 3) + quantity: 1, // Jumlah cetakan (default: 1) + labelType: 1, // Tipe label: + // 1 = WithGaps (label dengan jarak) + // 2 = Continuous (tanpa jarak) + // 3 = WithHoles (dengan lubang) + printTaskName: 'D110' // Model printer (D110, B21, B1, dll) + // Auto-detect jika tidak cocok +}); +``` + +### Ukuran Label +Di fungsi `createQRLabelCanvas()`: +```javascript +const labelWidth = 384; // pixel (40mm @ 240dpi) +const labelHeight = 384; // pixel +``` + +**Sesuaikan dengan ukuran label Anda:** +- 30mm x 30mm β‰ˆ 288 x 288 px +- 40mm x 40mm β‰ˆ 384 x 384 px +- 50mm x 30mm β‰ˆ 480 x 288 px + +Rumus: `mm Γ— (dpi / 25.4)` +- DPI printer Niimbot umumnya: 203 atau 240 + +--- + +## πŸ› Troubleshooting + +### 1. "Browser tidak mendukung koneksi printer" +**Solusi:** +- Gunakan Chrome atau Edge versi terbaru +- Update browser ke versi terkini +- Pastikan menggunakan HTTPS (Web Bluetooth hanya jalan di HTTPS) + +### 2. "Gagal terhubung ke printer" +**Solusi:** +- Pastikan printer sudah dinyalakan +- Pastikan Bluetooth/USB aktif +- Coba matikan dan nyalakan ulang printer +- Untuk Bluetooth: pastikan tidak paired ke device lain +- Coba refresh halaman dan ulangi koneksi + +### 3. "Printer terhubung tapi tidak print" +**Solusi:** +- Periksa kertas label sudah terpasang dengan benar +- Coba disconnect dan connect ulang +- Periksa battery printer (untuk model portable) +- Cek ukuran canvas sesuai dengan ukuran label + +### 4. "Print hasil blur/tidak jelas" +**Solusi:** +- Tingkatkan `density` (misal dari 3 ke 4) +- Pastikan threshold image sudah optimal +- Ukuran QR Code jangan terlalu kecil (min 150x150px) + +### 5. "Error: printTaskName not compatible" +**Solusi:** +- Ganti `printTaskName` sesuai model printer: + - D110: `'D110'` + - B21: `'B21'` + - B1: `'B1'` + - Atau hapus parameter, biarkan auto-detect + +--- + +## πŸ” Debugging + +### Enable Console Logs +Library sudah dilengkapi logging: +```javascript +// Di browser console akan muncul: +>> Packet sent: [hex bytes] +<< Packet received: [hex bytes] +Printer connected: { deviceName: "Niimbot-XXXX" } +Printer info fetched: { model: "D110", ... } +``` + +### Test Print Canvas +Tambahkan debug preview sebelum print: +```javascript +// Di createQRLabelCanvas, sebelum resolve(canvas): +document.body.appendChild(canvas); // Tampilkan canvas di halaman +canvas.style.border = '1px solid red'; +``` + +--- + +## πŸ“š Referensi API + +### useNiimbotPrinter Composable + +#### State (Reactive) +```javascript +const { + printerClient, // NiimbotClient instance + connectionState, // 'disconnected' | 'connecting' | 'connected' + connectedPrinterName, // string + printerInfo, // object { model, serial, ... } + printerMeta, // object { densityMin, densityMax, ... } + heartbeatData, // object { powerLevel, ... } + isPrinting, // boolean + printProgress, // number (0-100) + + // Computed + isConnected, // boolean + isDisconnected, // boolean + featureSupport, // { webBluetooth, webSerial, ... } +} = useNiimbotPrinter(); +``` + +#### Methods +```javascript +// Init client +initClient(type: 'bluetooth' | 'serial'): void + +// Connect/Disconnect +connect(): Promise +disconnect(): void + +// Print +printQRCode( + imageDataUrl: string, + options?: { + density?: number, // 1-5 + quantity?: number, // jumlah print + labelType?: number, // 1=WithGaps, 2=Continuous, 3=WithHoles + printTaskName?: string // 'D110', 'B21', dll + } +): Promise<{ success: boolean, message: string }> + +// Utils +loadImageToCanvas(dataUrl: string): Promise +applyThreshold(canvas: HTMLCanvasElement, threshold?: number): HTMLCanvasElement +``` + +--- + +## 🎨 Customization + +### Ubah Desain Label +Edit fungsi `createQRLabelCanvas()` di `BrankasList.vue`: + +```javascript +// Contoh: Tambah logo toko +const logo = new Image(); +logo.src = '/path/to/logo.png'; +logo.onload = () => { + ctx.drawImage(logo, 10, 10, 50, 50); +}; + +// Contoh: Tambah border +ctx.strokeStyle = 'black'; +ctx.lineWidth = 2; +ctx.strokeRect(5, 5, labelWidth - 10, labelHeight - 10); + +// Contoh: Font custom +ctx.font = 'bold 20px "Courier New"'; +``` + +### Tambah Barcode +Install library barcode: +```bash +npm install jsbarcode +``` + +Implementasi: +```javascript +import JsBarcode from 'jsbarcode'; + +const barcodeCanvas = document.createElement('canvas'); +JsBarcode(barcodeCanvas, item.kode_item, { + format: 'CODE128', + width: 2, + height: 40, +}); +ctx.drawImage(barcodeCanvas, x, y); +``` + +--- + +## πŸ“ Catatan Penting + +1. **HTTPS Required**: Web Bluetooth & Web Serial hanya bekerja di HTTPS (kecuali localhost) +2. **User Gesture**: Koneksi harus dipicu oleh user action (klik button), tidak bisa otomatis on load +3. **One Printer**: Satu browser session hanya bisa terhubung ke 1 printer +4. **Battery**: Printer portable akan disconnect otomatis jika battery rendah +5. **Label Size**: Sesuaikan ukuran canvas dengan ukuran label fisik untuk hasil optimal + +--- + +## πŸ†˜ Support + +Jika ada masalah: +1. Periksa console browser (F12 β†’ Console) +2. Periksa kompatibilitas browser +3. Periksa dokumentasi printer Niimbot Anda +4. Issue library: https://github.com/MultiMote/niimbluelib + +--- + +## πŸ“„ License + +Menggunakan library `@mmote/niimbluelib` yang bersifat open-source. +Pastikan mematuhi lisensi library saat deploy production. diff --git a/driver/NiimbotPrinterDriverInstall_3.0.0.5.exe b/driver/NiimbotPrinterDriverInstall_3.0.0.5.exe new file mode 100644 index 0000000..7c207fb Binary files /dev/null and b/driver/NiimbotPrinterDriverInstall_3.0.0.5.exe differ diff --git a/package-lock.json b/package-lock.json index 52da90b..2318044 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1874,9 +1874,9 @@ "license": "MIT" }, "node_modules/axios": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.11.0.tgz", - "integrity": "sha512-1Lx3WLFQWm3ooKDYZD1eXmoGO9fxYQjrycfHFC8P0sCfQVXyROp0p9PFWBehewBOdCwHc+f/b8I0fMto5eSfwA==", + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.12.2.tgz", + "integrity": "sha512-vMJzPewAlRyOgxV2dU0Cuz2O8zzzx9VYtbJOaBgXFeLc4IV/Eg50n4LowmehOOR61S8ZMpc2K5Sa7g6A4jfkUw==", "dev": true, "license": "MIT", "dependencies": { @@ -3700,14 +3700,14 @@ } }, "node_modules/tinyglobby": { - "version": "0.2.14", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.14.tgz", - "integrity": "sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==", + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", "dev": true, "license": "MIT", "dependencies": { - "fdir": "^6.4.4", - "picomatch": "^4.0.2" + "fdir": "^6.5.0", + "picomatch": "^4.0.3" }, "engines": { "node": ">=12.0.0" @@ -3805,9 +3805,9 @@ } }, "node_modules/vite": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/vite/-/vite-7.1.3.tgz", - "integrity": "sha512-OOUi5zjkDxYrKhTV3V7iKsoS37VUM7v40+HuwEmcrsf11Cdx9y3DIr2Px6liIcZFwt3XSRpQvFpL3WVy7ApkGw==", + "version": "7.1.10", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.1.10.tgz", + "integrity": "sha512-CmuvUBzVJ/e3HGxhg6cYk88NGgTnBoOo7ogtfJJ0fefUWAxN/WDSUa50o+oVBxuIhO8FoEZW0j2eW7sfjs5EtA==", "dev": true, "license": "MIT", "dependencies": { @@ -3816,7 +3816,7 @@ "picomatch": "^4.0.3", "postcss": "^8.5.6", "rollup": "^4.43.0", - "tinyglobby": "^0.2.14" + "tinyglobby": "^0.2.15" }, "bin": { "vite": "bin/vite.js" diff --git a/resources/js/components/BrankasList.vue b/resources/js/components/BrankasList.vue index 62d92eb..c03c6b6 100644 --- a/resources/js/components/BrankasList.vue +++ b/resources/js/components/BrankasList.vue @@ -3,15 +3,17 @@
Memuat data... - +
-