Introduction

Thermal printer AliExpress page

I recently purchased a thermal receipt printer off of AliExpress for a project. It features both WiFi and USB connectivity which I thought was really cool for the price.

To my dismay, I realized after purchasing that the drivers and configuration application only run on Windows.

This wasn't a huge deal, as thermal printers generally use the somewhat kinda standardized command set called ESC/POS. Unfortunately while many of the formatting commands are shared between printers, the commands to setup the WiFi connection don't seem to be documented anywhere, and I suspect are device-specific.

Since booting into Windows every time I want to manage the printer's network settings isn't ideal, I decided to reverse engineer the WiFi configuration commands.

Initially I tried to run the configuration tool in wine, but it couldn't communicate with the printer over USB, which wasn't too surprising.

Running in Windows

I booted my spare laptop into windows and launched the config tool there.

I then setup the WiFi through Advanced -> Set Net

Xprinter config tool running in wine Xprinter advanced settings Xprinter net settings

At this point I noticed that the application supported configuring the printer over the network, meaning I might be able to change the settings under wine again, as network sockets should work normally.

Wireshark on Linux

After rebooting into Linux and testing my assumption, it turned out to be half true, I was able to run the configuration tool over the network without issue and print from it, but I couldn't configure the WiFi.

While I could have stopped there I decided to go one step further and reverse the command sequence that configures the WiFi settings so I could re-configure the printer's WiFi over USB if it ever got messed up.

To do that, I sniffed the traffic going from the Xprinter application and the printer's socket using Wireshark, assuming the commands it sends over the network are the same ones it sends over USB.

xprinter_wireshark.png

In the examples below I've encoded the data being sent to hex to make it easier to understand the contents of the packets.

Based on the traffic, I was able to come up with the following.

Setting IP address

The following sets

  • IP 192.168.0.7

The IP hex:

[192, 168, 0, 7].map { _1.to_s(16).rjust(2, '0') }
c0 a8 00 07

Packet contents from wireshark:

0000 1f 1b 1f 22 c0 a8 00 07

Description Characters
Unit separator 1f
Escape 1b
Unit separator 1f
Command code 22
IP c0 a8 00 07

Setting subnet Mask

The following sets

  • Subnet mask 255.255.255.0

Subnet mask to hex:

[255, 255, 255, 0].map { _1.to_s(16).rjust(2, '0') }
ff ff ff 00

Packet contents from wireshark:

0000 1f 1b 1f b0 ff ff ff 00

Description Character
Unit separator 1f
Escape 1b
Unit separator 1f
Command code b0
Subnet mask ff ff ff 00

Setting gateway

The following sets

  • Gateway 192.168.0.1

Subnet mask to hex:

[192, 168, 0, 1].map { _1.to_s(16).rjust(2, '0') }
c0 a8 00 01

Packet contents from wireshark:

0000 1f 1b 1f b1 c0 a8 00 01

Description Character
Unit separator 1f
Escape 1b
Unit separator 1f
Command code b1
Net Mask c0 a8 00 01

Setting IP, subnet mask, and gateway

The following sets

  • IP 192.168.0.1

  • Subnet mask 255.255.255.0

  • Gateway 192.168.0.1

Packet contents from wireshark:

0000 1f 1b 1f b2 c0 a8 00 07 ff ff ff 00 c0 a8 00 01

Purpose Character
Unit separator 1f
Escape 1b
Unit separator 1f
Command code b2
IP c0 a8 00 07
Subnet mask ff ff ff 00
Gateway c0 a8 00 01

Setting WiFi network

The following sets

  • SSID SSID_HERE

  • Key PASSWORD_HERE

  • Key Type WPA2_AES_PSK

SSID to hex:

"SSID_HERE".bytes.map { _1.to_s(16) }
53 53 49 44 5f 48 45 52 45

Key to hex:

"PASSWORD_HERE".bytes.map { _1.to_s(16) }
50 41 53 53 57 4f 52 44 5f 48 45 52 45

Packet contents from wireshark (including string representation):

0000   1f 1b 1f b3 06 53 53 49 44 5f 48 45 52 45 00 50   .....SSID_HERE.P
0010   41 53 53 57 4f 52 44 5f 48 45 52 45 00            ASSWORD_HERE.
Purpose Character
Unit separator 1f
Escape 1b
Unit separator 1f
Command code b3
Key type 06
SSID SSID_HERE
NUL-termination 00
Key PASSWORD_HERE
NUL-termination 00

If the WiFi key type is anything like the menu, the other key types are as follows

Key Type Value
NULL 00
WEP64 01
WEP128 02
WPA_AES_PSK 03
WPA_TKIP_PSK 04
WPA_TKIP_AES_PSK 05
WPA2_AES_PSK 06
WPA2_TKIP 07
WPA2_TKIP_AES_PSK 08
WPA_WPA2_MixedMode 09

Setting all network options

The following sets

  • IP 192.168.0.7

  • Subnet mask 255.255.255.0

  • Gateway 192.168.0.1

  • SSID SSID_HERE

  • Key PASSWORD_HERE

  • Key Type WPA2_AES_PSK

Packet contents from wireshark (including string representation):

0000   1f 1b 1f b4 c0 a8 00 07 ff ff ff 00 c0 a8 00 01   ................
0010   06 53 53 49 44 5f 48 45 52 45 00 50 41 53 53 57   .SSID_HERE.PASSW
0020   4f 52 44 5f 48 45 52 45 00                        ORD_HERE.
Description Character
Unit Separator 1f
Escape 1b
Unit Separator 1f
Command Code b4
IP c0 a8 00 07
NetMask ff ff ff 00
Gateway c0 a8 00 01
Key Type 06
SSID SSID_HERE
NUL-termination 00
Key PASSWORD_HERE
NUL-termination 00

Post-packet analysis

At this point, after writing an application that could send identical packets given the correct input, I realized that for some reason, my printer was not responding to the commands issued from either the config utility or my program.

I tried to look deeper for better documentation, but was only able to come across this PDF from their Russian language website, which unfortunately still didn't contain the WiFi setup instructions.

I was also able to find this GitHub repo that seems to contain some commands for Xprinter systems, but not the ones I need.

The data sheets on the Xprinter website claims they have the Linux test utility, which should contain the necessary tools to configure wifi on the printers, but it seems they only support Android and Windows.

Wine USB attempt 2

I tried again to get the printer software to work under wine. It turns out wine only looks at /dev/lp* devices by default and doesn't add /dev/usb/lp*. This time I searched the wine wiki for ways to get the Linux /dev/usb/lp0 device to show up as LPT1 under wine. After some digging it appears you can tell wine which devices to map to COM / LPT ports with registry values.

It's described in section 4.3.1 on the Wine User's Guide.

I followed the guide and created the following registry key.

wine regedit

I then restarted the wine server using the following command.

wineserver -k

I've exported the registry entry here in case anyone wants to do the same.

At that point the Xprinter setup tool was able to recognize the printer as LPT1.

I then setup wireshark to be able to sniff USB traffic using the their guide here.

From there I was able to figure out which USB hub it was running through and its device ID, and filter it out using a wireshark filter.

wireshark USB sniffing

After sniffing the USB traffic sent by the xprinter configuration app, it looks identical to what was being sent over the TCP connection, meaning what I built should have worked.

The solution

So apparently I made some assumptions when starting this project that turned out to not be true.

It seems only the command to set all options at once consistently works, even when using the Xprinter setup tool from within Windows. The printer will also not allow you to reconfigure it's WiFi unless connected over USB.

After I narrowed down the number of commands I was testing to only xb4 and only trying over USB, it worked fine.

I suppose I should have checked that all the commands worked properly from the beginning, but I did learn a lot along the way so it wasn't a total loss.

After figuring out the issue, I wrote a small command line tool to configure the printer. You can check it out here.

Xprinter WiFi config tool screenshot