Cetak ESC/POS Epson TM-T82 dari Aplikasi POS Sendiri

Tidak secara native. Printer menjalankan perintah ESC/POS, yaitu urutan byte yang diawali ESC (heksa 1B) atau GS (heksa 1D). Driver memang bisa meraster halaman HTML atau PDF menjadi bitmap lalu mengalirkannya sebagai titik, tetapi jalur itu jauh lebih lambat dan tidak memberi kendali atas pemotong maupun laci uang. Untuk point of sale, hasilkan byte ESC/POS secara langsung.
Pada kertas 80 mm, lebar cetaknya 72 mm pada 203 dpi. Font A memakai sel 12 kali 24 titik sehingga menghasilkan 48 karakter per baris, sedangkan Font B 9 kali 17 titik menghasilkan 64. Pada kertas 58 mm, Font A yang sama memberi sekitar 32 kolom. Karena fontnya monospace, tata letak Anda benar-benar berupa hitungan karakter.
Printer tidak memahami UTF-8. Ia mencetak dari tabel karakter satu byte yang dipilih lewat perintah ESC t, sehingga setiap byte dari urutan UTF-8 multi-byte menjadi glif tersendiri. Encode teks Anda ke code page yang sedang aktif — PC437 adalah default yang umum — atau pilih tabel yang memuat karakter yang Anda butuhkan sebelum mencetak blok tersebut.
Transport mana pun yang terpasang secara fisik: device node USB atau serial, atau koneksi TCP ke port 9100 pada model Ethernet. Di Windows Anda juga bisa mendorong byte mentah melewati antrean cetak dengan datatype RAW, sehingga manajemen antrean tetap ada tanpa proses rasterisasi. Buka satu koneksi per struk dan tutup setelah selesai.
Tidak. Simpan payload struk dan id pembayaran ke tabel antrean di dalam transaksi yang sama dengan penutupan penjualan, lalu biarkan satu worker menguras antrean itu. Printer yang mati, macet, atau kehabisan kertas kemudian menjadi pekerjaan antrean yang terlihat, bukan layar checkout yang menggantung, dan cetak ulang menjadi percobaan ulang terhadap id pembayaran yang sama.

Ringkasan Utama
Epson TM-T82 mencetak aliran byte ESC/POS, bukan halaman web. POS custom yang andal menyusun struk sebagai byte, mengirimkannya langsung ke printer lewat USB, serial, atau TCP port 9100, dan menata teks dalam baris tetap 48 kolom alih-alih mengandalkan dialog cetak browser.
Struk pertama yang saya kirim ke Epson TM-T82 keluar sebagai teks Courier dengan margin dua sentimeter, terpotong tepat di tengah baris total, dan butuh sebelas detik untuk tercetak. HTML-nya benar. Struknya tidak berguna. Jarak antara cara web developer memahami pencetakan dan cara printer POS memahaminya menghabiskan hampir seminggu waktu saya, dan seluruh isi tulisan ini adalah yang seharusnya saya baca sebelum mulai.
Ini pandangan level byte: apa sebenarnya perangkat kerasnya, bagaimana bahasa perintahnya bekerja, mengapa aritmetika kolom menggantikan CSS, dan di mana teks Indonesia diam-diam rusak. Semua klaim di sini berasal dari dokumen teknis resmi Epson atau dari printer yang ada di meja saya.
Jalur yang menggoda adalah merender struk sebagai HTML, memanggil window.print, lalu memilih TM-T82 dari dialog. Cara itu berhasil saat demo dan gagal di toko, karena empat alasan yang sifatnya struktural, bukan sekadar bug.
Alternatifnya tidak lebih sulit, hanya kurang familier: Anda menyusun array byte dan menuliskannya ke sebuah device. Tidak ada tahap rendering sama sekali, dan justru itulah alasan cara ini cepat sekaligus dapat diprediksi.

Sebelum menulis kode, ada baiknya mengetahui angka yang menjadi batasan desain Anda. Spesifikasi resmi Epson untuk keluarga T82 cukup konkret, dan hampir semua batasan tata letak yang nanti Anda temui bisa dijelaskan dari sana.
| Properti | Nilai pada keluarga TM-T82 |
|---|---|
| Metode cetak | Thermal line printing — satu baris titik horizontal sekali jalan, tanpa tinta, tanpa pita |
| Kecepatan | Hingga 150 mm per detik pada TM-T82, kira-kira satu struk penuh per detik |
| Resolusi | 203 x 203 dpi, yaitu 8 titik per mm, dengan lebar cetak 72 mm pada kertas 80 mm |
| Grid karakter | Font A berukuran 12 x 24 titik menghasilkan 48 kolom; Font B 9 x 17 titik menghasilkan 64 kolom |
| Ketahanan | Sekitar 60 juta baris MCBF dan auto cutter dengan umur sekitar 1,5 juta potongan |
Baca baris grid karakter dua kali, karena itu fakta paling berguna di artikel ini. Printer tidak punya font proporsional dan tidak bisa menaruh teks di posisi sembarang: setiap baris adalah deretan sel identik, dan seluruh anggaran tata letak Anda adalah 48 sel. Rancang di dalam grid itu dan semuanya rapi; lawan grid itu dan tidak ada yang rapi.

ESC/POS adalah protokol byte yang diperkenalkan Epson untuk printer TM dan kini ditiru hampir seluruh industri. Perintah diawali ESC, heksa 1B, atau GS, heksa 1D, diikuti sebuah huruf dan parameternya; apa pun yang bukan perintah dicetak apa adanya. Itu saja model mentalnya.
// The whole "hello receipt" in raw ESC/POS. No driver, no dialog.
const ESC = 0x1b, GS = 0x1d, LF = 0x0a;
const bytes = Buffer.concat([
Buffer.from([ESC, 0x40]), // ESC @ initialize
Buffer.from([ESC, 0x74, 0x00]), // ESC t 0 code page 437
Buffer.from([ESC, 0x61, 0x01]), // ESC a 1 centre
Buffer.from([GS, 0x21, 0x11]), // GS ! double height + width
Buffer.from([ESC, 0x45, 0x01]), // ESC E 1 emphasized on
Buffer.from("TOKO SUMBER REJEKI\n", "ascii"),
Buffer.from([ESC, 0x45, 0x00]), // ESC E 0 emphasized off
Buffer.from([GS, 0x21, 0x00]), // GS ! 0 back to normal size
Buffer.from([ESC, 0x61, 0x00]), // ESC a 0 left
Buffer.from("Jl. Diponegoro 12, Semarang\n", "ascii"),
Buffer.from("-".repeat(48) + "\n", "ascii"),
Buffer.from([LF, LF, LF, LF]), // feed past the tear bar
Buffer.from([GS, 0x56, 0x42, 0x00]), // GS V B 0 partial cut with feed
]);Perhatikan apa yang tidak ada: tidak ada page setup, tidak ada margin, tidak ada pemuatan font, dan tidak ada konfirmasi. Anda inisialisasi, set mode, kirim teks, feed, potong. Printer menjalankan perintah sesuai urutan kedatangannya di buffer — itu juga sebabnya perintah potong yang ditaruh terlalu awal akan memotong baris total.
Simpan satu hexdump struk yang sudah terbukti benar sebagai fixture di repositori. Ketika struk bermasalah di produksi, mem-diff byte yang Anda kirim dengan fixture itu menemukan bug dalam hitungan detik, dan fixture-nya bertahan melewati semua refactor kode penghasil byte.
Dengan 48 sel tetap per baris, merapikan angka ke kanan bukan urusan gaya, melainkan pengurangan. Setiap helper tata letak di pustaka pencetakan POS pada dasarnya adalah versi dari dua fungsi di bawah, dan menulisnya sendiri memakan sepuluh menit sekaligus menghapus satu dependensi.
const COLUMNS_FONT_A = 48; // 80 mm paper, 72 mm print width, 12x24 font
// One item line: name on the left, money hard against the right margin.
function itemLine(name: string, amount: string): string {
const room = COLUMNS_FONT_A - amount.length - 1;
const label = name.length > room ? name.slice(0, room - 1) + "." : name;
return label.padEnd(room + 1, " ") + amount;
}
itemLine("Kopi Susu Gula Aren", "25.000");
// "Kopi Susu Gula Aren 25.000"
// Wrap, never truncate silently, when the product name is genuinely long.
function wrapName(name: string, width = COLUMNS_FONT_A - 10): string[] {
const words = name.split(" ");
return words.reduce<string[]>((lines, word) => {
const last = lines[lines.length - 1];
if (last && (last + " " + word).length <= width) {
lines[lines.length - 1] = last + " " + word;
} else {
lines.push(word);
}
return lines;
}, []);
}Dua aturan menghemat sebagian besar penderitaan. Pertama, jangan biarkan nama produk memotong harga di ujung baris: pesan dulu lebar untuk nominal, sisanya baru untuk nama. Kedua, tentukan sekali apakah nama panjang dibungkus ke baris berikutnya atau dipotong dengan titik, lalu terapkan di mana-mana, karena struk yang sebagian barisnya membungkus dan sebagian tidak terbaca rusak meski aritmetikanya benar.
Printer tidak memahami UTF-8. Ia menyimpan sekumpulan tabel karakter satu byte dan mencetak tabel mana pun yang sedang aktif, dipilih dengan ESC t. Kirim byte UTF-8 tanpa berpikir, dan karakter beraksen akan muncul sebagai pasangan simbol, karena setiap byte dari urutan multi-byte dicetak sebagai glif tersendiri.
Simbol mata uang layak mendapat keputusan tersendiri. Saya mencetak nominal sebagai digit berkelompok tanpa simbol dan menaruh kata Rp di baris header, sehingga tidak bergantung pada ada tidaknya glif rupiah di suatu code page dan digitnya tetap sejajar pada sel yang sama di setiap baris.
Nominal rupiah adalah satu-satunya tempat di mana bug tampilan berubah menjadi sengketa keuangan. Format angka sekali saja, dalam satu fungsi, dengan pemisah ribuan yang eksplisit, lalu cetak hasil fungsi itu di mana-mana — struk, layar, dan laporan — agar pelanggan yang memegang kertas dan manajer yang membaca dasbor tidak mungkin melihat total yang berbeda.
Mencetak adalah I/O ke perangkat fisik yang bisa macet, kehabisan kertas, dan tercabut oleh siapa pun yang sedang mengepel. Alur di bawah ini adalah yang akhirnya saya pakai setelah sebulan penuh keluhan.
Struktur itu sekaligus memberi Anda metrik yang penting secara operasional: berapa struk yang mengantre dan berapa umur antrean tertua. Printer yang mati empat menit muncul sebagai angka sebelum muncul sebagai antrean pelanggan yang kesal.
Saya terlalu lama memaksa satu jalur kode melayani printer meja 80 mm dan printer mobile 58 mm sekaligus. Bahasa perintahnya memang sama, tetapi 48 kolom dan 32 kolom adalah dua tata letak berbeda, bukan satu tata letak dengan ukuran berbeda, dan memaksakannya menghasilkan struk yang secara teknis benar namun terlihat salah di kedua perangkat.
Saya juga akan menguji dengan kertas murah sejak hari pertama. Semua struk terlihat bagus di atas gulungan contoh bawaan kotak. Gulungan yang benar-benar dibeli toko lebih tipis dan kurang sensitif, dan justru itulah yang membuktikan apakah pilihan kontras dan penebalan Anda bertahan.
Mencetak ke TM-T82 berhenti terasa sulit begitu Anda berhenti memperlakukannya sebagai printer dalam arti desktop dan mulai memperlakukannya sebagai perangkat serial yang menerima bahasa perintah kecil. Susun byte-nya, kuasai transport-nya, hormati grid 48 kolom, dan perangkat keras ini justru menjadi bagian paling andal dari seluruh sistem point-of-sale.