Open-source Linux/macOS driver for the POLONO P31S thermal label printer.
The POLONO P31S is a compact, battery-powered thermal label printer designed for portable use. It prints on small 14×40mm adhesive label stickers, making it ideal for organizing cables, labeling storage containers, price tags, or any application requiring small durable labels.
Key hardware specs:
- Print technology: Direct thermal (no ink required)
- Resolution: 203 DPI
- Label size: 14×40mm (printable area: ~120×320 pixels)
- Connectivity: Bluetooth Low Energy (BLE)
- Power: Rechargeable battery
- Price: ~$20-30 USD
The printer uses a subset of the TSPL (TSC Printer Language) protocol over BLE, which this driver implements.
- Print images to 40x14mm labels at 203 DPI (120x320 pixels max)
- Query printer status, battery level, and firmware version
- Simple CLI and Python API
- Uses TSPL text commands over Bluetooth Low Energy
# Create virtual environment
python3 -m venv venv
source venv/bin/activate
# Install in development mode
pip install -e .
# Or install dependencies only
pip install bleak pillow click# Using CLI
p31s scan
# Or using the discovery tool
python tools/discover.py# Get detailed GATT service info
python tools/discover.py --address XX:XX:XX:XX:XX:XXp31s print XX:XX:XX:XX:XX:XX image.png
p31s print XX:XX:XX:XX:XX:XX image.png --density 12 --copies 2p31s test XX:XX:XX:XX:XX:XXThe raw command sends arbitrary hex data directly to the printer for debugging purposes:
p31s raw XX:XX:XX:XX:XX:XX "1b21"--force to skip the confirmation prompt:
p31s raw XX:XX:XX:XX:XX:XX "1b21" --forceimport asyncio
from p31s import P31SPrinter
async def main():
# Scan for printers
printers = await P31SPrinter.scan()
print(f"Found: {printers}")
# Connect and print
printer = P31SPrinter()
if await printer.connect(printers[0].address):
await printer.print_image("label.png")
await printer.disconnect()
asyncio.run(main())p31/
├── src/p31s/ # Main library
│ ├── printer.py # High-level API (P31SPrinter class)
│ ├── connection.py # BLE connection (scan, chunked writes)
│ ├── tspl.py # TSPL command builder
│ ├── tspl_commands.py # Status queries (CONFIG?, BATTERY?)
│ ├── responses.py # Response parsers
│ └── cli.py # Command-line interface
├── tools/ # Development utilities
│ └── discover.py # BLE scanner/debugger
├── docs/
│ └── protocol.md # Protocol documentation
└── tests/ # Unit tests
Run unit tests (no hardware required):
# Install test dependencies
pip install -e ".[dev]"
pip install pytest-asyncio
# Run unit tests only
pytest tests/ -m "not hardware"Integration tests require a real P31S printer connected via Bluetooth:
# First, find your printer's address
p31s scan
# Run all tests including hardware tests
pytest tests/ -m hardware --address=XX:XX:XX:XX:XX:XX
# Run specific test categories
pytest tests/test_integration.py -m hardware --address=XX:XX:XX:XX:XX:XX -k "TestConnection"
pytest tests/test_integration.py -m hardware --address=XX:XX:XX:XX:XX:XX -k "TestPrint"Warning: Print tests (TestPrint) will actually print labels. Make sure you have paper loaded.
See docs/protocol.md for the complete protocol specification.
- bleak - Bluetooth Low Energy library
- pillow - Image processing
- click - CLI framework
# Add user to bluetooth group
sudo usermod -aG bluetooth $USER
# Log out and back in
# Or run with sudo for testing
sudo python tools/discover.pyGrant Bluetooth access when prompted by the system.
Apache 2.0 - see LICENSE
