Printing on a Bluetooth Thermal Printer from Chrome

I own one of those cat Bluetooth thermal printers. Short after I bought it, I started wondering if I could print to it from my computer, and I did a quick research a couple of times, but never really put much effort on it.

More recently, I started thinking about working on a custom web-based POS app for a small restaurant (Yes, I know there is a ton of ready-made apps already). Most of what that type of applications require is very simple, but the thermal printer interaction would be something that would need to be solved. I started doing some research again, and this time I took it a bit more seriously.

As it turns out, it is actually quite simple to “talk” to thermal printers. Searching for “thermal printer protocol” I learned that they usually use a simple set of instructions know as ESC/POS, and for basic printing, like the one required by a POS app, the basic commands are enough. There is even a plugin that allows you to print to thermal printer from anything that can do HTTP, and a PHP library that implements a subset of the ESC/POS protocol. However, when I tried to print using my printer, things weren’t quite as simple.

The BLE Complication

The solutions I had encounter so far did not seem to be aimed to BLE (Bluetooth Low Energy) printers. At this point I had to first figure out how to communicate with the printer, in order to send it commands.

The first issue I had was that my computer (a Mac) did not show the printer in the Bluetooth devices list. While searching for how to connect a thermal printer on a mac, I found an interesting question in stack overflow about connecting to thermal printers via the Web Bluetooth API. One of the answers there mentions the about:bluetooth-internals page that chrome has available for inspecting and connecting to nearby Bluetooth devices. Chrome did show the printer, so I connected to it, and started playing a little bit, but I couldn’t really make much sense of what I was seeing there.

BLE specs.

Disclaimer: I am not an expert in any way on this topic. Please do your own research.

BLE implements GATT, which uses the concepts of Services, and Characteristics. Services, and characteristics are identified by UUIDs. Services group sets of characteristics together, and characteristics are used to communicate (read/write) with the device. In google chrome you can inspect a devices’ services, and characteristics, as well as read from and write to characteristics.

I played a little with chrome, trying to send different ESC commands to the printer, but nothing seemed to work. Eventually, I started trying with bluetoothctl in linux, but I kept getting errors back saying the first character was not valid. At the time I was sending the ESC character as the first command, since that is what I had learned on the various resources I had read.

Learning from an existing implementation

I found that someone had already written a C++ library for arduino that works with cat printers. On that library I found the UUID of the characteristic I should send commands to, but I didn’t take the time to fully understand the code, or to even read it all. I only searched for the parts I thought I needed, and kept trying to send commands to the printer. Eventually, I called it a day, and went to bed.

The next day I went straight back to trying to get it to work. This time, however, I read the arduino library code more carefully, and learned the key piece I was missing: cat printers use their own protocol.

At this point I knew the two key parts that would eventually allow me to get the printer to work: 1) The BLE characteristic to which I should be writing commands, and 2) that cat printers use their own protocol. I was, however, missing a key ingredient: understanding of the C++ code. I spent another day trying, and failing. Most of the time was spent trying to modify the C++ code so that it would print the characters that I should send to the printer, but my lack of knowledge was making it hard for me to successfully modify the code. At this point I decided to stop, and get my feet wet in the C++ grammar.

C++: learning the basics

I spent a couple of days learning C++ in my spare time, but with the weekend gone, that spare time was limited. I watched an intro tutorial on youTube, and read bits and pieces of the C++ reference. This allowed me to eventually modify the code successfully to get the characters printed on the screen for me to copy & paste on google Chrome.

The code

I finally ended up with the following code, which is a shameless copy of a small subset of the arduino library modified to print the characters that should be sent to the printer. It has to be manually modified to set the string you want to print, as well as that string’s length, but since I just wanted a proof of concept, that was sufficient at the time. Just to state how incomplete this is, I never even compiled the code, I just ran it using https://www.mycompiler.io/.

#include <iostream>

const uint8_t cChecksumTable[] = {
	0x00, 0x07, 0x0e, 0x09, 0x1c, 0x1b, 0x12, 0x15,  0x38, 0x3f, 0x36, 0x31, 0x24, 0x23, 0x2a, 0x2d, 
	0x70, 0x77, 0x7e, 0x79, 0x6c, 0x6b, 0x62, 0x65,  0x48, 0x4f, 0x46, 0x41, 0x54, 0x53, 0x5a, 0x5d, 
	0xe0, 0xe7, 0xee, 0xe9, 0xfc, 0xfb, 0xf2, 0xf5,  0xd8, 0xdf, 0xd6, 0xd1, 0xc4, 0xc3, 0xca, 0xcd, 
	0x90, 0x97, 0x9e, 0x99, 0x8c, 0x8b, 0x82, 0x85,  0xa8, 0xaf, 0xa6, 0xa1, 0xb4, 0xb3, 0xba, 0xbd, 
	0xc7, 0xc0, 0xc9, 0xce, 0xdb, 0xdc, 0xd5, 0xd2,  0xff, 0xf8, 0xf1, 0xf6, 0xe3, 0xe4, 0xed, 0xea, 
	0xb7, 0xb0, 0xb9, 0xbe, 0xab, 0xac, 0xa5, 0xa2,  0x8f, 0x88, 0x81, 0x86, 0x93, 0x94, 0x9d, 0x9a, 
	0x27, 0x20, 0x29, 0x2e, 0x3b, 0x3c, 0x35, 0x32,  0x1f, 0x18, 0x11, 0x16, 0x03, 0x04, 0x0d, 0x0a, 
	0x57, 0x50, 0x59, 0x5e, 0x4b, 0x4c, 0x45, 0x42,  0x6f, 0x68, 0x61, 0x66, 0x73, 0x74, 0x7d, 0x7a, 
	0x89, 0x8e, 0x87, 0x80, 0x95, 0x92, 0x9b, 0x9c,  0xb1, 0xb6, 0xbf, 0xb8, 0xad, 0xaa, 0xa3, 0xa4, 
	0xf9, 0xfe, 0xf7, 0xf0, 0xe5, 0xe2, 0xeb, 0xec,  0xc1, 0xc6, 0xcf, 0xc8, 0xdd, 0xda, 0xd3, 0xd4, 
	0x69, 0x6e, 0x67, 0x60, 0x75, 0x72, 0x7b, 0x7c,  0x51, 0x56, 0x5f, 0x58, 0x4d, 0x4a, 0x43, 0x44, 
	0x19, 0x1e, 0x17, 0x10, 0x05, 0x02, 0x0b, 0x0c,  0x21, 0x26, 0x2f, 0x28, 0x3d, 0x3a, 0x33, 0x34, 
	0x4e, 0x49, 0x40, 0x47, 0x52, 0x55, 0x5c, 0x5b,  0x76, 0x71, 0x78, 0x7f, 0x6a, 0x6d, 0x64, 0x63, 
	0x3e, 0x39, 0x30, 0x37, 0x22, 0x25, 0x2c, 0x2b,  0x06, 0x01, 0x08, 0x0f, 0x1a, 0x1d, 0x14, 0x13, 
	0xae, 0xa9, 0xa0, 0xa7, 0xb2, 0xb5, 0xbc, 0xbb,  0x96, 0x91, 0x98, 0x9f, 0x8a, 0x8d, 0x84, 0x83, 
	0xde, 0xd9, 0xd0, 0xd7, 0xc2, 0xc5, 0xcc, 0xcb,  0xe6, 0xe1, 0xe8, 0xef, 0xfa, 0xfd, 0xf4, 0xf3};

const unsigned char ucMirror[256]=
     {0x00, 0x80, 0x40, 0xC0, 0x20, 0xA0, 0x60, 0xE0, 0x10, 0x90, 0x50, 0xD0, 0x30, 0xB0, 0x70, 0xF0,
      0x08, 0x88, 0x48, 0xC8, 0x28, 0xA8, 0x68, 0xE8, 0x18, 0x98, 0x58, 0xD8, 0x38, 0xB8, 0x78, 0xF8,
      0x04, 0x84, 0x44, 0xC4, 0x24, 0xA4, 0x64, 0xE4, 0x14, 0x94, 0x54, 0xD4, 0x34, 0xB4, 0x74, 0xF4,
      0x0C, 0x8C, 0x4C, 0xCC, 0x2C, 0xAC, 0x6C, 0xEC, 0x1C, 0x9C, 0x5C, 0xDC, 0x3C, 0xBC, 0x7C, 0xFC,
      0x02, 0x82, 0x42, 0xC2, 0x22, 0xA2, 0x62, 0xE2, 0x12, 0x92, 0x52, 0xD2, 0x32, 0xB2, 0x72, 0xF2,
      0x0A, 0x8A, 0x4A, 0xCA, 0x2A, 0xAA, 0x6A, 0xEA, 0x1A, 0x9A, 0x5A, 0xDA, 0x3A, 0xBA, 0x7A, 0xFA,
      0x06, 0x86, 0x46, 0xC6, 0x26, 0xA6, 0x66, 0xE6, 0x16, 0x96, 0x56, 0xD6, 0x36, 0xB6, 0x76, 0xF6,
      0x0E, 0x8E, 0x4E, 0xCE, 0x2E, 0xAE, 0x6E, 0xEE, 0x1E, 0x9E, 0x5E, 0xDE, 0x3E, 0xBE, 0x7E, 0xFE,
      0x01, 0x81, 0x41, 0xC1, 0x21, 0xA1, 0x61, 0xE1, 0x11, 0x91, 0x51, 0xD1, 0x31, 0xB1, 0x71, 0xF1,
      0x09, 0x89, 0x49, 0xC9, 0x29, 0xA9, 0x69, 0xE9, 0x19, 0x99, 0x59, 0xD9, 0x39, 0xB9, 0x79, 0xF9,
      0x05, 0x85, 0x45, 0xC5, 0x25, 0xA5, 0x65, 0xE5, 0x15, 0x95, 0x55, 0xD5, 0x35, 0xB5, 0x75, 0xF5,
      0x0D, 0x8D, 0x4D, 0xCD, 0x2D, 0xAD, 0x6D, 0xED, 0x1D, 0x9D, 0x5D, 0xDD, 0x3D, 0xBD, 0x7D, 0xFD,
      0x03, 0x83, 0x43, 0xC3, 0x23, 0xA3, 0x63, 0xE3, 0x13, 0x93, 0x53, 0xD3, 0x33, 0xB3, 0x73, 0xF3,
      0x0B, 0x8B, 0x4B, 0xCB, 0x2B, 0xAB, 0x6B, 0xEB, 0x1B, 0x9B, 0x5B, 0xDB, 0x3B, 0xBB, 0x7B, 0xFB,
      0x07, 0x87, 0x47, 0xC7, 0x27, 0xA7, 0x67, 0xE7, 0x17, 0x97, 0x57, 0xD7, 0x37, 0xB7, 0x77, 0xF7,
      0x0F, 0x8F, 0x4F, 0xCF, 0x2F, 0xAF, 0x6F, 0xEF, 0x1F, 0x9F, 0x5F, 0xDF, 0x3F, 0xBF, 0x7F, 0xFF};

unsigned char ucFont[] = {
	// ANSI 437 character set
	0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x18,0x3c,0x3c,0x18,0x18,0x00,0x18,0x00,	// " " "!" (#32, 33)
	0x6c,0x6c,0x6c,0x00,0x00,0x00,0x00,0x00, 0x6c,0x6c,0xfe,0x6c,0xfe,0x6c,0x6c,0x00,	// " # (34, 35)
	0x18,0x7e,0xc0,0x7c,0x06,0xfc,0x18,0x00, 0x00,0xc6,0xcc,0x18,0x30,0x66,0xc6,0x00,	// $ % (36, 37)
	0x38,0x6c,0x38,0x76,0xdc,0xcc,0x76,0x00, 0x30,0x30,0x60,0x00,0x00,0x00,0x00,0x00,	// & ' (38, 39)
	0x18,0x30,0x60,0x60,0x60,0x30,0x18,0x00, 0x60,0x30,0x18,0x18,0x18,0x30,0x60,0x00,	// ( ) (40, 41)
	0x00,0x66,0x3c,0xff,0x3c,0x66,0x00,0x00, 0x00,0x18,0x18,0x7e,0x18,0x18,0x00,0x00,	// * + (42, 43)
	0x00,0x00,0x00,0x00,0x00,0x18,0x18,0x30, 0x00,0x00,0x00,0x7e,0x00,0x00,0x00,0x00,	// , - (44, 45)
	0x00,0x00,0x00,0x00,0x00,0x18,0x18,0x00, 0x06,0x0c,0x18,0x30,0x60,0xc0,0x80,0x00,	// . / (46, 47)
	0x7c,0xce,0xde,0xf6,0xe6,0xc6,0x7c,0x00, 0x30,0x70,0x30,0x30,0x30,0x30,0xfc,0x00,	// 0 1 (48, 49)
	0x78,0xcc,0x0c,0x38,0x60,0xcc,0xfc,0x00, 0x78,0xcc,0x0c,0x38,0x0c,0xcc,0x78,0x00,	// 2 3 (50, 51)
	0x1c,0x3c,0x6c,0xcc,0xfe,0x0c,0x1e,0x00, 0xfc,0xc0,0xf8,0x0c,0x0c,0xcc,0x78,0x00,	// 4 5 (52, 53)
	0x38,0x60,0xc0,0xf8,0xcc,0xcc,0x78,0x00, 0xfc,0xcc,0x0c,0x18,0x30,0x30,0x30,0x00,	// 6 7 (54, 55)
	0x78,0xcc,0xcc,0x78,0xcc,0xcc,0x78,0x00, 0x78,0xcc,0xcc,0x7c,0x0c,0x18,0x70,0x00,	// 8 9 (56, 57)
	0x00,0x18,0x18,0x00,0x00,0x18,0x18,0x00, 0x00,0x18,0x18,0x00,0x00,0x18,0x18,0x30,	// : ; (58, 59)
	0x18,0x30,0x60,0xc0,0x60,0x30,0x18,0x00, 0x00,0x00,0x7e,0x00,0x7e,0x00,0x00,0x00,	// < = (60, 61)
	0x60,0x30,0x18,0x0c,0x18,0x30,0x60,0x00, 0x3c,0x66,0x0c,0x18,0x18,0x00,0x18,0x00,	// > ? (62, 63)
	0x7c,0xc6,0xde,0xde,0xdc,0xc0,0x7c,0x00, 0x30,0x78,0xcc,0xcc,0xfc,0xcc,0xcc,0x00,	// @ A (64, 65)
	0xfc,0x66,0x66,0x7c,0x66,0x66,0xfc,0x00, 0x3c,0x66,0xc0,0xc0,0xc0,0x66,0x3c,0x00,	// B C (66, 67)
	0xf8,0x6c,0x66,0x66,0x66,0x6c,0xf8,0x00, 0xfe,0x62,0x68,0x78,0x68,0x62,0xfe,0x00,	// D E (68, 69)
	0xfe,0x62,0x68,0x78,0x68,0x60,0xf0,0x00, 0x3c,0x66,0xc0,0xc0,0xce,0x66,0x3a,0x00,	// F G (70, 71)
	0xcc,0xcc,0xcc,0xfc,0xcc,0xcc,0xcc,0x00, 0x78,0x30,0x30,0x30,0x30,0x30,0x78,0x00,	// H I (72, 73)
	0x1e,0x0c,0x0c,0x0c,0xcc,0xcc,0x78,0x00, 0xe6,0x66,0x6c,0x78,0x6c,0x66,0xe6,0x00,	// J K (74, 75)
	0xf0,0x60,0x60,0x60,0x62,0x66,0xfe,0x00, 0xc6,0xee,0xfe,0xfe,0xd6,0xc6,0xc6,0x00,	// L M (76, 77)
	0xc6,0xe6,0xf6,0xde,0xce,0xc6,0xc6,0x00, 0x38,0x6c,0xc6,0xc6,0xc6,0x6c,0x38,0x00,	// N O (78, 79)
	0xfc,0x66,0x66,0x7c,0x60,0x60,0xf0,0x00, 0x7c,0xc6,0xc6,0xc6,0xd6,0x7c,0x0e,0x00,	// P Q (80, 81)
	0xfc,0x66,0x66,0x7c,0x6c,0x66,0xe6,0x00, 0x7c,0xc6,0xe0,0x78,0x0e,0xc6,0x7c,0x00,	// R S (82, 83)
	0xfc,0xb4,0x30,0x30,0x30,0x30,0x78,0x00, 0xcc,0xcc,0xcc,0xcc,0xcc,0xcc,0xfc,0x00,	// T U (84, 85)
	0xcc,0xcc,0xcc,0xcc,0xcc,0x78,0x30,0x00, 0xc6,0xc6,0xc6,0xc6,0xd6,0xfe,0x6c,0x00,	// V W (86, 87)
	0xc6,0xc6,0x6c,0x38,0x6c,0xc6,0xc6,0x00, 0xcc,0xcc,0xcc,0x78,0x30,0x30,0x78,0x00,	// X Y (88, 89)
	0xfe,0xc6,0x8c,0x18,0x32,0x66,0xfe,0x00, 0x78,0x60,0x60,0x60,0x60,0x60,0x78,0x00,	// Z [ (90, 91)
	0xc0,0x60,0x30,0x18,0x0c,0x06,0x02,0x00, 0x78,0x18,0x18,0x18,0x18,0x18,0x78,0x00,	// \ ] (92, 93)
	0x10,0x38,0x6c,0xc6,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,	// ^ _ (94, 95)
	0x30,0x30,0x18,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x78,0x0c,0x7c,0xcc,0x76,0x00,	// ` a (96, 97)
	0xe0,0x60,0x60,0x7c,0x66,0x66,0xdc,0x00, 0x00,0x00,0x78,0xcc,0xc0,0xcc,0x78,0x00,	// b c (98, 99)
	0x1c,0x0c,0x0c,0x7c,0xcc,0xcc,0x76,0x00, 0x00,0x00,0x78,0xcc,0xfc,0xc0,0x78,0x00,	// d e (100, 101)
	0x38,0x6c,0x64,0xf0,0x60,0x60,0xf0,0x00, 0x00,0x00,0x76,0xcc,0xcc,0x7c,0x0c,0xf8,	// f g (102, 103)
	0xe0,0x60,0x6c,0x76,0x66,0x66,0xe6,0x00, 0x30,0x00,0x70,0x30,0x30,0x30,0x78,0x00,	// h i (104, 105)
	0x0c,0x00,0x1c,0x0c,0x0c,0xcc,0xcc,0x78, 0xe0,0x60,0x66,0x6c,0x78,0x6c,0xe6,0x00,	// j k (106, 107)
	0x70,0x30,0x30,0x30,0x30,0x30,0x78,0x00, 0x00,0x00,0xcc,0xfe,0xfe,0xd6,0xd6,0x00,	// l m (108, 109)
	0x00,0x00,0xb8,0xcc,0xcc,0xcc,0xcc,0x00, 0x00,0x00,0x78,0xcc,0xcc,0xcc,0x78,0x00,	// n o (110, 111)
	0x00,0x00,0xdc,0x66,0x66,0x7c,0x60,0xf0, 0x00,0x00,0x76,0xcc,0xcc,0x7c,0x0c,0x1e,	// p q (112, 113)
	0x00,0x00,0xdc,0x76,0x62,0x60,0xf0,0x00, 0x00,0x00,0x7c,0xc0,0x70,0x1c,0xf8,0x00,	// r s (114, 115)
	0x10,0x30,0xfc,0x30,0x30,0x34,0x18,0x00, 0x00,0x00,0xcc,0xcc,0xcc,0xcc,0x76,0x00,	// t u (116, 117)
	0x00,0x00,0xcc,0xcc,0xcc,0x78,0x30,0x00, 0x00,0x00,0xc6,0xc6,0xd6,0xfe,0x6c,0x00,	// v w (118, 119)
	0x00,0x00,0xc6,0x6c,0x38,0x6c,0xc6,0x00, 0x00,0x00,0xcc,0xcc,0xcc,0x7c,0x0c,0xf8,	// x y (120, 121)
	0x00,0x00,0xfc,0x98,0x30,0x64,0xfc,0x00, 0x1c,0x30,0x30,0xe0,0x30,0x30,0x1c,0x00,	// z { (122, 123)
	0x18,0x18,0x18,0x00,0x18,0x18,0x18,0x00, 0xe0,0x30,0x30,0x1c,0x30,0x30,0xe0,0x00,	// | } (124, 125)
	0x76,0xdc,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x10,0x38,0x6c,0xc6,0xc6,0xfe,0x00,	// ~ ⌂ (126, 127) del is printed as house
	0x7c,0xc6,0xc0,0xc6,0x7c,0x0c,0x06,0x7c, 0x00,0xcc,0x00,0xcc,0xcc,0xcc,0x76,0x00,	// Ç ü (128, 129) Ç ü	left is ANSI 437 character set
	0x1c,0x00,0x78,0xcc,0xfc,0xc0,0x78,0x00, 0x7e,0x81,0x3c,0x06,0x3e,0x66,0x3b,0x00,	// é â (130, 131) é â	right is change to CP852 (Latin-2)
	0xcc,0x00,0x78,0x0c,0x7c,0xcc,0x76,0x00, 0xe0,0x00,0x78,0x0c,0x7c,0xcc,0x76,0x00,	// ä à (132, 133) ä ů
	0x30,0x30,0x78,0x0c,0x7c,0xcc,0x76,0x00, 0x00,0x00,0x7c,0xc6,0xc0,0x78,0x0c,0x38,	// å ç (134, 135) ć ç
	0x7e,0x81,0x3c,0x66,0x7e,0x60,0x3c,0x00, 0xcc,0x00,0x78,0xcc,0xfc,0xc0,0x78,0x00,	// ê ë (136, 137) ł ë
	0xe0,0x00,0x78,0xcc,0xfc,0xc0,0x78,0x00, 0xcc,0x00,0x70,0x30,0x30,0x30,0x78,0x00,	// è ï (138, 139) Ő ő
	0x7c,0x82,0x38,0x18,0x18,0x18,0x3c,0x00, 0xe0,0x00,0x70,0x30,0x30,0x30,0x78,0x00,	// î ì (140, 141) î Ź
	0xc6,0x10,0x7c,0xc6,0xfe,0xc6,0xc6,0x00, 0x30,0x30,0x00,0x78,0xcc,0xfc,0xcc,0x00,	// Ä Å (142, 143) Ä Ć
	0x1c,0x00,0xfc,0x60,0x78,0x60,0xfc,0x00, 0x00,0x00,0x7f,0x0c,0x7f,0xcc,0x7f,0x00,	// É æ (144, 145) É Ĺ
	0x3e,0x6c,0xcc,0xfe,0xcc,0xcc,0xce,0x00, 0x78,0x84,0x00,0x78,0xcc,0xcc,0x78,0x00,	// Æ ô (146, 147) Í ô
	0x00,0xcc,0x00,0x78,0xcc,0xcc,0x78,0x00, 0x00,0xe0,0x00,0x78,0xcc,0xcc,0x78,0x00,	// ö ò (148, 149) ö Ľ
	0x78,0x84,0x00,0xcc,0xcc,0xcc,0x76,0x00, 0x00,0xe0,0x00,0xcc,0xcc,0xcc,0x76,0x00,	// û ù (150, 151) ľ Ś
	0x00,0xcc,0x00,0xcc,0xcc,0x7c,0x0c,0xf8, 0xc3,0x18,0x3c,0x66,0x66,0x3c,0x18,0x00,	// ÿ Ö (152, 153) ś Ö
	0xcc,0x00,0xcc,0xcc,0xcc,0xcc,0x78,0x00, 0x18,0x18,0x7e,0xc0,0xc0,0x7e,0x18,0x18,	// Ü ¢ (154, 155) Ü Ť
	0x38,0x6c,0x64,0xf0,0x60,0xe6,0xfc,0x00, 0xcc,0xcc,0x78,0x30,0xfc,0x30,0xfc,0x30,	// £ ¥ (156, 157) ť Ł
	0xf8,0xcc,0xcc,0xfa,0xc6,0xcf,0xc6,0xc3, 0x0e,0x1b,0x18,0x3c,0x18,0x18,0xd8,0x70,	// ₧ ƒ (158, 159) × č
	0x1c,0x00,0x78,0x0c,0x7c,0xcc,0x76,0x00, 0x38,0x00,0x70,0x30,0x30,0x30,0x78,0x00,	// á í (160, 161) á í
	0x00,0x1c,0x00,0x78,0xcc,0xcc,0x78,0x00, 0x00,0x1c,0x00,0xcc,0xcc,0xcc,0x76,0x00,	// ó ú (162, 163) ó ú
	0x00,0xf8,0x00,0xb8,0xcc,0xcc,0xcc,0x00, 0xfc,0x00,0xcc,0xec,0xfc,0xdc,0xcc,0x00,	// ñ Ñ (164, 165) Ą ą
	0x3c,0x6c,0x6c,0x3e,0x00,0x7e,0x00,0x00, 0x38,0x6c,0x6c,0x38,0x00,0x7c,0x00,0x00,	// ạ ọ (166, 167) Ž ž
	0x18,0x00,0x18,0x18,0x30,0x66,0x3c,0x00, 0x00,0x00,0x00,0xfc,0xc0,0xc0,0x00,0x00,	// ¿ ⌐ (168, 169) Ę ę
	0x00,0x00,0x00,0xfc,0x0c,0x0c,0x00,0x00, 0xc6,0xcc,0xd8,0x36,0x6b,0xc2,0x84,0x0f,	// ¬ ½ (170, 171) ¬ ź
	0xc3,0xc6,0xcc,0xdb,0x37,0x6d,0xcf,0x03, 0x18,0x00,0x18,0x18,0x3c,0x3c,0x18,0x00,	// ¼ ¡ (172, 173) Č ş
	0x00,0x33,0x66,0xcc,0x66,0x33,0x00,0x00, 0x00,0xcc,0x66,0x33,0x66,0xcc,0x00,0x00,	// « » (174, 175) 
	0x22,0x88,0x22,0x88,0x22,0x88,0x22,0x88, 0x55,0xaa,0x55,0xaa,0x55,0xaa,0x55,0xaa,	// ░ ▒ (176, 177)
	0xdb,0xf6,0xdb,0x6f,0xdb,0x7e,0xd7,0xed, 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,	// ▓ │ (178, 179)
	0x18,0x18,0x18,0x18,0xf8,0x18,0x18,0x18, 0x18,0x18,0xf8,0x18,0xf8,0x18,0x18,0x18,	// ┤ ╡ (180, 181) ┤ Á
	0x36,0x36,0x36,0x36,0xf6,0x36,0x36,0x36, 0x00,0x00,0x00,0x00,0xfe,0x36,0x36,0x36,	// ╢ ╖ (182, 183) Â Ě
	0x00,0x00,0xf8,0x18,0xf8,0x18,0x18,0x18, 0x36,0x36,0xf6,0x06,0xf6,0x36,0x36,0x36,	// ╕ ╣ (184, 185) Ş ╣
	0x36,0x36,0x36,0x36,0x36,0x36,0x36,0x36, 0x00,0x00,0xfe,0x06,0xf6,0x36,0x36,0x36,	// ║ ╗ (186, 187) ║ ╗
	0x36,0x36,0xf6,0x06,0xfe,0x00,0x00,0x00, 0x36,0x36,0x36,0x36,0xfe,0x00,0x00,0x00,	// ╝ ╜ (188, 189) ╝ Ż
	0x18,0x18,0xf8,0x18,0xf8,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0xf8,0x18,0x18,0x18,	// ╛ ┐ (190, 191) ż ┐
	0x18,0x18,0x18,0x18,0x1f,0x00,0x00,0x00, 0x18,0x18,0x18,0x18,0xff,0x00,0x00,0x00,	// └ ┴ (192, 193) └ ┴
	0x00,0x00,0x00,0x00,0xff,0x18,0x18,0x18, 0x18,0x18,0x18,0x18,0x1f,0x18,0x18,0x18,	// ┬ ├ (194, 195) ┬ ├
	0x00,0x00,0x00,0x00,0xff,0x00,0x00,0x00, 0x18,0x18,0x18,0x18,0xff,0x18,0x18,0x18,	// ─ ┼ (196, 197) ─ ┼
	0x18,0x18,0x1f,0x18,0x1f,0x18,0x18,0x18, 0x36,0x36,0x36,0x36,0x37,0x36,0x36,0x36,	// ╞ ╟ (198, 199) Ă ă
	0x36,0x36,0x37,0x30,0x3f,0x00,0x00,0x00, 0x00,0x00,0x3f,0x30,0x37,0x36,0x36,0x36,	// ╚ ╔ (200, 201) ╚ ╔
	0x36,0x36,0xf7,0x00,0xff,0x00,0x00,0x00, 0x00,0x00,0xff,0x00,0xf7,0x36,0x36,0x36,	// ╩ ╦ (202, 203) ╩ ╦
	0x36,0x36,0x37,0x30,0x37,0x36,0x36,0x36, 0x00,0x00,0xff,0x00,0xff,0x00,0x00,0x00,	// ╠ ═ (204, 205) ╠ ═
	0x36,0x36,0xf7,0x00,0xf7,0x36,0x36,0x36, 0x18,0x18,0xff,0x00,0xff,0x00,0x00,0x00,	// ╬ ╧ (206, 207) ╬ ¤
	0x36,0x36,0x36,0x36,0xff,0x00,0x00,0x00, 0x00,0x00,0xff,0x00,0xff,0x18,0x18,0x18,	// ╨ ╤ (208, 209) đ Đ
	0x00,0x00,0x00,0x00,0xff,0x36,0x36,0x36, 0x36,0x36,0x36,0x36,0x3f,0x00,0x00,0x00,	// ╥ ╙ (210, 211) Ď Ë
	0x18,0x18,0x1f,0x18,0x1f,0x00,0x00,0x00, 0x00,0x00,0x1f,0x18,0x1f,0x18,0x18,0x18,	// ╘ ╒ (212, 213) ď Ň
	0x00,0x00,0x00,0x00,0x3f,0x36,0x36,0x36, 0x36,0x36,0x36,0x36,0xff,0x36,0x36,0x36,	// ╓ ╫ (214, 215) Í î
	0x18,0x18,0xff,0x18,0xff,0x18,0x18,0x18, 0x18,0x18,0x18,0x18,0xf8,0x00,0x00,0x00,	// ╪ ┘ (216, 217) ě ┘
	0x00,0x00,0x00,0x00,0x1f,0x18,0x18,0x18, 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,	// ┌ █ (218, 219) ┌ █
	0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff, 0xf0,0xf0,0xf0,0xf0,0xf0,0xf0,0xf0,0xf0,	// ▄ ▌ (220, 221) ▄ Ţ
	0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f, 0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,	// ▐ ▀ (222, 223) Ů ▀
	0x00,0x00,0x76,0xdc,0xc8,0xdc,0x76,0x00, 0x00,0x78,0xcc,0xf8,0xcc,0xf8,0xc0,0xc0,	// α β (224, 225) Ó β
	0x00,0xfc,0xcc,0xc0,0xc0,0xc0,0xc0,0x00, 0x00,0x00,0xfe,0x6c,0x6c,0x6c,0x6c,0x00,	// Γ π (226, 227) Ô Ń
	0xfc,0xcc,0x60,0x30,0x60,0xcc,0xfc,0x00, 0x00,0x00,0x7e,0xd8,0xd8,0xd8,0x70,0x00,	// Σ σ (228, 229) ń ň
	0x00,0x66,0x66,0x66,0x66,0x7c,0x60,0xc0, 0x00,0x76,0xdc,0x18,0x18,0x18,0x18,0x00,	// μ Τ (230, 231) Š š
	0xfc,0x30,0x78,0xcc,0xcc,0x78,0x30,0xfc, 0x38,0x6c,0xc6,0xfe,0xc6,0x6c,0x38,0x00,	// ϕ Ө (232, 233) Ŕ Ú
	0x38,0x6c,0xc6,0xc6,0x6c,0x6c,0xee,0x00, 0x1c,0x30,0x18,0x7c,0xcc,0xcc,0x78,0x00,	// Ω δ (234, 235) ŕ Ű
	0x00,0x00,0x7e,0xdb,0xdb,0x7e,0x00,0x00, 0x06,0x0c,0x7e,0xdb,0xdb,0x7e,0x60,0xc0,	// ∞ ø (236, 237) ý Ý
	0x38,0x60,0xc0,0xf8,0xc0,0x60,0x38,0x00, 0x78,0xcc,0xcc,0xcc,0xcc,0xcc,0xcc,0x00,	//   ∩ (238, 239) ţ ´
	0x00,0x7e,0x00,0x7e,0x00,0x7e,0x00,0x00, 0x18,0x18,0x7e,0x18,0x18,0x00,0x7e,0x00,	// ≡ ± (240, 241) ­ ̋
	0x60,0x30,0x18,0x30,0x60,0x00,0xfc,0x00, 0x18,0x30,0x60,0x30,0x18,0x00,0xfc,0x00,	// ≥ ≤ (242, 243) ̨ ˇ
	0x0e,0x1b,0x1b,0x18,0x18,0x18,0x18,0x18, 0x18,0x18,0x18,0x18,0x18,0xd8,0xd8,0x70,	// ⌠ ⌡ (244, 245) ̆ §
	0x18,0x18,0x00,0x7e,0x00,0x18,0x18,0x00, 0x00,0x76,0xdc,0x00,0x76,0xdc,0x00,0x00,	// ÷ ꞊ (246, 247) ÷ ̧
	0x38,0x6c,0x6c,0x38,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x18,0x18,0x00,0x00,0x00,	// ○ ● (248, 249) ° ¨
	0x00,0x00,0x00,0x00,0x18,0x00,0x00,0x00, 0x0f,0x0c,0x0c,0x0c,0xec,0x6c,0x3c,0x1c,	// ▪ √ (250, 251) ̇ ű
	0x58,0x6c,0x6c,0x6c,0x6c,0x00,0x00,0x00, 0x70,0x98,0x30,0x60,0xf8,0x00,0x00,0x00,	// ⁿ ² (252, 253) Ř ř
	0x00,0x00,0x3c,0x3c,0x3c,0x3c,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00};	// ■   (254, 255) ■
	
static uint8_t CheckSum(uint8_t *pData, int iLen)
{
int i;
uint8_t cs = 0;

    for (i=0; i<iLen; i++)
        cs = cChecksumTable[(cs ^ pData[i])];
    return cs;
}

void tpPrintCatTextLine()
{
    // Send drawing mode command to cat printer.
    // 0X5178BE0001000107FF

    int CatStrLen = 20;
    char CatStr[] = "";
    uint8_t ucTemp[56];
    for (int j=0; j<8; j++) // pixel row of text
    {
      ucTemp[0] = 0x51;
      ucTemp[1] = 0x78;
      ucTemp[2] = 0xA2; // data, uncompressed
      ucTemp[3] = 0;
      ucTemp[4] = CatStrLen; // data length
      ucTemp[5] = 0;
      for (int i=0; i<CatStrLen; i++)
        ucTemp[6+i] = ucMirror[ucFont[((CatStr[i]-32)*8)+j]];
      ucTemp[6 + CatStrLen] = CheckSum(&ucTemp[6], CatStrLen);
      ucTemp[6 + CatStrLen + 1] = 0xFF;
      //tpWriteData(ucTemp, 8 + CatStrLen);
      
      for (int k = 0; k < CatStrLen + 8; k++) {
        printf("%02x", ucTemp[k]);
      }
    }
    
    
    
}

int main() {
    tpPrintCatTextLine();
    return 0;
}

What’s next

I want to port the code to Javascript, either in node, or in the browser, just to have a complete implementation of a piece of code that can print to a cat printer from the browser, or the console. Eventually, this will all be just a small personal exercise, because for now, the ultimate goal is to have a thermal printer permanently connected to my in-house server so that it can print important messages, and I don’t think a cat printer is the right choice for that. I will buy a USB printer that talks ESC/POS to do that. Ultimately, that will become the final proof of concept for the implementation of the printing component for that POS app I want to build.

Other source:
https://parzibyte.me/blog/en/2019/10/10/print-receipt-thermal-printer-javascript-css-html/
https://www.visuality.pl/posts/thermal-printer-protocols-for-image-and-text
https://mike42.me/blog/what-is-escpos-and-how-do-i-use-it
https://developer.chrome.com/articles/bluetooth/#tips
https://en.wikipedia.org/wiki/Bluetooth_Low_Energy
https://stackoverflow.com/questions/30662024/how-to-send-esc-pos-commands-to-thermal-printer-in-linux
https://mike42.me/blog/2015-03-getting-a-usb-receipt-printer-working-on-linux