Introduction
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
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.
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.
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.
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.