#+TITLE: Setting up an Xprinter thermal receipt printer WiFi from Linux #+DATE: 2021-10-12T15:57:12-04:00 #+DRAFT: true #+DESCRIPTION: #+TAGS[]: #+KEYWORDS[]: #+SLUG: #+SUMMARY: I recently purchased a thermal receipt printer off of [[https://web.archive.org/web/20211012195845/https://www.aliexpress.com/item/32842111016.html?spm=a2g0s.9042311.0.0.207d4c4d6xNWgO][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 [[https://github.com/escpos/escpos][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. I booted my spare laptop into windows and launched the config tool there. I then setup the WiFi through =Advanced -> Set Net= #+ATTR_HTML: :title Xprinter config tool running in wine #+ATTR_HTML: :alt Xprinter config tool running in wine [[file:xprinter_setup_tool.png]] #+ATTR_HTML: :title Xprinter advanced settings #+ATTR_HTML: :alt Xprinter advanced settings [[file:xprinter_advanced.png]] #+ATTR_HTML: :title Xprinter net settings #+ATTR_HTML: :alt Xprinter net settings [[file:xprinter_set_net.png]] 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. After rebooting into Linux and testing my assumption, it turned out to be true, I was able to run the configuration tool without issue as long as it was connected through the network. 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. [[file: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: #+begin_src ruby [192, 168, 0, 7].map { _1.to_s(16).rjust(2, '0') } #+end_src #+RESULTS: | 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: #+begin_src ruby [255, 255, 255, 0].map { _1.to_s(16).rjust(2, '0') } #+end_src #+RESULTS: | 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: #+begin_src ruby [192, 168, 0, 1].map { _1.to_s(16).rjust(2, '0') } #+end_src #+RESULTS: | 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, netmask, 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: #+begin_src ruby "SSID_HERE".bytes.map { _1.to_s(16) } #+end_src #+RESULTS: | 53 | 53 | 49 | 44 | 5f | 48 | 45 | 52 | 45 | Key to hex: #+begin_src ruby "PASSWORD_HERE".bytes.map { _1.to_s(16) } #+end_src #+RESULTS: | 50 | 41 | 53 | 53 | 57 | 4f | 52 | 44 | 5f | 48 | 45 | 52 | 45 | Packet contents from wireshark (including string representation): #+begin_src 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. #+end_src | 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): #+begin_src 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. #+end_src | 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= |