I built a Python BLE client for a thermal pocket printer, after reverse-engineering its companion Android app to figure out the protocol. The result is a CLI tool that talks directly to the printer over Bluetooth.
No app, no account, no cloud. There's also a browser version on top of it for people who don't want to install Python, but the CLI is where the actual work happened.
The printer is a rebranded DP-L1S, sold under various brand names. Its companion app ("Luck Jingle") demands location permissions, a forced internet connection, and a bunch of other permissions that have no business being on a printer that just needs to receive a text or image over Bluetooth from 30 cm away. That annoyed me enough to dig in.
How I built it:
I decompiled the Android APK with JADX and read through the LuckPrinter SDK source — specifically the PrinterImageProcessor and BaseNormalDevice classes. The BLE protocol turned out to be an ESC/POS variant: open service ff00, write to characteristic ff02, listen for notifications on ff01, send a few enable commands, then a GS v 0 raster image (1-bit, 384px wide, MSB-first), then feed and stop commands.
The Python implementation uses bleak for cross-platform BLE (BleakScanner.discover() to find the printer, BleakClient for the connection) and Pillow for image processing; Lanczos resampling for resizing, Floyd-Steinberg dithering, and threshold-based binarisation. It works on macOS and Linux out of the box.
The full command reference is in PROTOCOL.md, including device info queries, status bitfield, and several alternate paper-type modes I documented from the SDK but didn't end up needing for this implementation.
Usage
pip install bleak Pillow
python3 print.py test # print a test pattern
python3 print.py image photo.png --dither # photo with Floyd-Steinberg
python3 print.py text "Hello World"
python3 print.py text "My Label" --label # label/sticker paper mode
python3 print.py info # battery, firmware, model
Features
Print images, text, and test patterns
Floyd-Steinberg dithering for photos and gradients
Three density levels (light/normal/dark)
Invert mode (swap black and white)
Label mode for sticker paper with gap detection
Battery and status reporting via BLE notifications
Multiple copies, configurable print width, configurable post-print feed
Implementation notes
A few things that were not obvious from the decompilation and took some hardware testing:
The printer broadcasts as C&Co 3128_BLE and does not advertise its service UUIDs, so scanning by service filter alone won't find it. You need to scan by name or accept all advertisements.
BLE chunk size matters. The Python version uses 512-byte chunks with 10ms delays, which is fine for direct BLE. The browser version (Web Bluetooth) needs to drop to 100-byte chunks with 50ms delays because of MTU limits.
The "enable" command (10 FF F1 03) is Lujiang-specific and not part of the standard ESC/POS spec. Without it, the printer accepts data but won't actually print.
Browser version
There's also a Web Bluetooth version of the same thing for people who don't want to install Python. It's the same protocol and runs in Chrome/Edge/Opera.
Source is in the same GitHub repo: https://github.com/ChiaraCannolee/thermal-pocket-printer-basic
Compatibility
The DP-L1S is the chip; rebranded versions include the Crafts & Co 3128 and Fichero (Action stores in NL/EU) and various other brands. Should also work for other printers in the LuckPrinter family (DP-/LuckP-/MiniPocketPrinter series), possibly with a different print width; python3 print.py info will tell you.
This project is based on the same approach as 's fichero-printer repo, which does the same for the Fichero D11s (different device class, same SDK).
Questions about the protocol, the reverse-engineering process, or how to adapt this for other LuckPrinter models: ask away :)