summaryrefslogtreecommitdiffstats
path: root/content/posts/WIP-xprinter-wifi/index.org
diff options
context:
space:
mode:
Diffstat (limited to 'content/posts/WIP-xprinter-wifi/index.org')
-rw-r--r--content/posts/WIP-xprinter-wifi/index.org497
1 files changed, 296 insertions, 201 deletions
diff --git a/content/posts/WIP-xprinter-wifi/index.org b/content/posts/WIP-xprinter-wifi/index.org
index 8bba537..2f17363 100644
--- a/content/posts/WIP-xprinter-wifi/index.org
+++ b/content/posts/WIP-xprinter-wifi/index.org
@@ -1,256 +1,351 @@
-#+TITLE: Setting up an Xprinter thermal receipt printer WiFi from Linux
+#+TITLE: Reverse engineering a thermal printer's WiFi setup
#+DATE: 2021-10-12T15:57:12-04:00
#+DRAFT: true
-#+DESCRIPTION:
-#+TAGS[]:
-#+KEYWORDS[]:
+#+DESCRIPTION: Setting up an Xprinter thermal receipt printer WiFi from Linux
+#+TAGS[]: linux hardware
+#+KEYWORDS[]: linux hardware
#+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.
+* Introduction
-To my dismay, I realized after purchasing that the drivers and
-configuration application only run on Windows.
+ 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.
-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.
+ To my dismay, I realized after purchasing that the drivers and
+ configuration application only run on Windows.
-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.
+ 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.
-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.
+ 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.
-I booted my spare laptop into windows and launched the config tool
-there.
+ 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 then setup the WiFi through =Advanced -> Set Net=
+* Running in Windows
-#+ATTR_HTML: :title Xprinter config tool running in wine
-#+ATTR_HTML: :alt Xprinter config tool running in wine
-[[file:xprinter_setup_tool.png]]
+ 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 advanced settings
-#+ATTR_HTML: :alt Xprinter advanced settings
-[[file:xprinter_advanced.png]]
+ #+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 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.
+ #+ATTR_HTML: :title Xprinter advanced settings
+ #+ATTR_HTML: :alt Xprinter advanced settings
+ [[file:xprinter_advanced.png]]
-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.
+ #+ATTR_HTML: :title Xprinter net settings
+ #+ATTR_HTML: :alt Xprinter net settings
+ [[file:xprinter_set_net.png]]
-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.
+ 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.
-To do that, I sniffed the traffic going from the Xprinter application
-and the printer's socket using Wireshark.
+* Wireshark on Linux
-[[file:xprinter_wireshark.png]]
+ 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.
-In the examples below I've encoded the data being sent to hex to make
-it easier to understand the contents of the packets.
+ 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.
-Based on the traffic, I was able to come up with the following.
+ 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.
-* Setting IP address
+ [[file:xprinter_wireshark.png]]
- The following sets
- - IP =192.168.0.7=
+ In the examples below I've encoded the data being sent to hex to make
+ it easier to understand the contents of the packets.
- The IP hex:
- #+begin_src ruby
- [192, 168, 0, 7].map { _1.to_s(16).rjust(2, '0') }
- #+end_src
+ Based on the traffic, I was able to come up with the following.
- #+RESULTS:
- | c0 | a8 | 00 | 07 |
+** Setting IP address
- Packet contents from wireshark:
+ The following sets
+ - IP =192.168.0.7=
- =0000 1f 1b 1f 22 c0 a8 00 07=
+ The IP hex:
+ #+begin_src ruby
+ [192, 168, 0, 7].map { _1.to_s(16).rjust(2, '0') }
+ #+end_src
- | Description | Characters |
- |----------------+---------------|
- | Unit separator | =1f= |
- | Escape | =1b= |
- | Unit separator | =1f= |
- | Command code | =22= |
- | IP | =c0 a8 00 07= |
+ #+RESULTS:
+ | c0 | a8 | 00 | 07 |
-* Setting Subnet Mask
+ Packet contents from wireshark:
- The following sets
- - Subnet mask =255.255.255.0=
+ =0000 1f 1b 1f 22 c0 a8 00 07=
- Subnet mask to hex:
- #+begin_src ruby
- [255, 255, 255, 0].map { _1.to_s(16).rjust(2, '0') }
- #+end_src
+ | Description | Characters |
+ |----------------+---------------|
+ | Unit separator | =1f= |
+ | Escape | =1b= |
+ | Unit separator | =1f= |
+ | Command code | =22= |
+ | IP | =c0 a8 00 07= |
- #+RESULTS:
- | ff | ff | ff | 00 |
+** Setting subnet Mask
- Packet contents from wireshark:
+ The following sets
+ - Subnet mask =255.255.255.0=
- =0000 1f 1b 1f b0 ff ff ff 00=
+ Subnet mask to hex:
+ #+begin_src ruby
+ [255, 255, 255, 0].map { _1.to_s(16).rjust(2, '0') }
+ #+end_src
- | Description | Character |
- |----------------+---------------|
- | Unit separator | =1f= |
- | Escape | =1b= |
- | Unit separator | =1f= |
- | Command code | =b0= |
- | Subnet mask | =ff ff ff 00= |
+ #+RESULTS:
+ | ff | ff | ff | 00 |
-* Setting gateway
+ Packet contents from wireshark:
- The following sets
- - Gateway =192.168.0.1=
+ =0000 1f 1b 1f b0 ff ff ff 00=
- Subnet mask to hex:
- #+begin_src ruby
- [192, 168, 0, 1].map { _1.to_s(16).rjust(2, '0') }
- #+end_src
+ | Description | Character |
+ |----------------+---------------|
+ | Unit separator | =1f= |
+ | Escape | =1b= |
+ | Unit separator | =1f= |
+ | Command code | =b0= |
+ | Subnet mask | =ff ff ff 00= |
- #+RESULTS:
- | c0 | a8 | 00 | 01 |
+** Setting gateway
- Packet contents from wireshark:
+ The following sets
+ - Gateway =192.168.0.1=
- =0000 1f 1b 1f b1 c0 a8 00 01=
+ Subnet mask to hex:
+ #+begin_src ruby
+ [192, 168, 0, 1].map { _1.to_s(16).rjust(2, '0') }
+ #+end_src
- | Description | Character |
- |----------------+---------------|
- | Unit separator | =1f= |
- | Escape | =1b= |
- | Unit separator | =1f= |
- | Command code | =b1= |
- | Net Mask | =c0 a8 00 01= |
+ #+RESULTS:
+ | c0 | a8 | 00 | 01 |
-* Setting IP, netmask, and gateway
+ Packet contents from wireshark:
- The following sets
- - IP =192.168.0.1=
- - Subnet mask =255.255.255.0=
- - Gateway =192.168.0.1=
+ =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:
+ #+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= |
+
+* 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 [[file:80XX_Programmer_Manual.pdf][this PDF]] from their [[https://xprinter.com.ua/terms--conditions.html][Russian language website]], which
+ unfortunately still didn't contain the WiFi setup instructions.
+
+ I was also able to find [[https://github.com/daotuyen9244/XprinterPOS][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 [[https://wiki.winehq.org/Wine_User's_Guide#Serial_and_Parallel_Ports][4.3.1]] on the [[https://wiki.winehq.org/Wine_User's_Guide][Wine User's Guide]].
+
+ I followed the guide and created the following registry key.
+
+ #+ATTR_HTML: :title Adding the registry key in wine
+ #+ATTR_HTML: :alt wine regedit
+ [[file:xprinter_wine_regedit.png]]
+
+ I then restarted the wine server using the following command.
+
+ #+begin_src shell
+ wineserver -k
+ #+end_src
- Packet contents from wireshark:
+ I've exported the registry entry [[file:xprinter_lp0.reg][here]] in case anyone wants to do the
+ same.
- =0000 1f 1b 1f b2 c0 a8 00 07 ff ff ff 00 c0 a8 00 01=
+ At that point the Xprinter setup tool was able to recognize the
+ printer as =LPT1=.
- | 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= |
+ I then setup wireshark to be able to sniff USB traffic using the
+ their guide [[https://wiki.wireshark.org/CaptureSetup/USB][here]].
-* Setting WiFi network
+ 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.
- The following sets
- - SSID =SSID_HERE=
- - Key =PASSWORD_HERE=
- - Key Type =WPA2_AES_PSK=
+ #+ATTR_HTML: :title wireshark USB sniffing
+ #+ATTR_HTML: :alt wireshark USB sniffing
+ [[file:xprinter_usb_wireshark.png]]
- SSID to hex:
- #+begin_src ruby
- "SSID_HERE".bytes.map { _1.to_s(16) }
- #+end_src
+ 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.
- #+RESULTS:
- | 53 | 53 | 49 | 44 | 5f | 48 | 45 | 52 | 45 |
+* The solution
- Key to hex:
- #+begin_src ruby
- "PASSWORD_HERE".bytes.map { _1.to_s(16) }
- #+end_src
+ So apparently I made some assumptions when starting this project
+ that turned out to not be true.
- #+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
+ 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 [[https://github.com/dantecatalfamo/xprinter-wifi][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= |
+ #+ATTR_HTML: :title Xprinter WiFi config tool screenshot
+ #+ATTR_HTML: :alt Xprinter WiFi config tool screenshot
+ [[file:xprinter_wifi_screenshot.png]]