LX-D02 BLE thermal printer: PC, Windows printer, Home Assistant

- printer.py: protocol with flow control (pause / resend / done)
- lxd02.py: print text or pictures from the command line
- windows/: POS-58 driver + local ESC/POS server = a regular Windows printer,
  Explorer menu for pictures
- custom_components/lxd02: Home Assistant integration with a persistent queue

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Alex Cube
2026-10-21 16:56:45 +03:00
co-authored by Claude Opus 5.5
commit 9e06f3ebd0
12 changed files with 765 additions and 0 deletions
+186
View File
@@ -0,0 +1,186 @@
# lx-d02
Печать на Bluetooth-термопринтере **LX-D02** (он же Funny Print, Xiqi, DOLEWA и другие
бренды) без приложения на смартфоне:
- **как на обычном принтере Windows**: из Блокнота, браузера, Word, из меню «Печать»;
- **картинки правым кликом** в проводнике, растянутые на всю ширину ленты;
- **из командной строки**: `python lxd02.py "Привет"`;
- **из Home Assistant**: действие `lxd02.print_text` с очередью, которая переживает
выключенный принтер и перезапуск HA.
Принтер работает только по BLE (Bluetooth Low Energy): USB-порт у него лишь для зарядки,
и Windows без этого проекта его как принтер не видит.
Протокол разобран в [ValdikSS/printer-driver-funnyprint](https://github.com/ValdikSS/printer-driver-funnyprint),
управление потоком сделано по образцу [klimromanyuk/funnyprint](https://github.com/klimromanyuk/funnyprint).
Подробный рассказ, как это сделано: [блог «Доктор Лексиум»](https://www.lexium.ru/).
## Что нужно
- Python 3.9+ и Bluetooth на компьютере.
- `pip install -r requirements.txt`
- Принтер включён и **не подключён к телефону**: LX-D02 держит только одно соединение,
и пока открыто Funny Print, компьютер его не найдёт.
Проверка: `python lxd02.py "Привет"` должен напечатать строку.
На Python 3.9 bleak 1.x падает при импорте, поэтому в `requirements.txt` стоит `bleak<1.0`.
На более новом Python ограничение можно снять.
## Печать из командной строки
```
python lxd02.py "Первая строка" "Вторая строка"
python lxd02.py фото.jpg скриншот.png
```
Текст печатается шрифтом DejaVu Sans (кириллица есть). Картинки растягиваются на ширину
ленты (48 мм, 384 точки), альбомные поворачиваются вдоль ленты, чтобы выйти крупнее,
полутона передаются растром.
Принтер ищется по имени `LX-D02`. Чтобы не тратить на поиск несколько секунд, задайте MAC:
`set LXD02_MAC=AA:BB:CC:DD:EE:FF` (MAC покажет любой BLE-сканер, например nRF Connect
на телефоне).
## Принтер в Windows
Как это устроено: Windows печатает на принтер с драйвером **POS-58** (стандартный драйвер
58-мм чековых принтеров), драйвер раскладывает страницу на ленту 58 мм и отдаёт её
командами ESC/POS в порт `127.0.0.1:9100`. Там их принимает `windows/print_server.py`
и печатает по Bluetooth.
Почему не встроенные драйверы Windows: «Microsoft Print to PDF» знает только офисные
форматы, и лист A4, ужатый до 48 мм, нечитаем. IPP Class Driver принимает только
стандартные названия бумаги, самое узкое из которых A6.
### Драйвер
Драйвер POS-58 обычно идёт с любым дешёвым 58-мм чековым принтером, ищите
«POS-58 driver» на сайте Xprinter или продавца. Проверено с `POS-58 11.2.0.0`
(Zjiang, `pos58.inf`). Сам принтер POS-58 не нужен, нужен только драйвер.
### Установка
В PowerShell **от имени администратора**:
```powershell
Set-ExecutionPolicy -Scope Process Bypass
.\windows\install.ps1
```
Скрипт:
- добавляет принтер **LX-D02** на драйвере POS-58 и порту `127.0.0.1:9100`;
- ставит в Планировщик задание «LX-D02 print server» (сервер стартует при входе в систему);
- добавляет в меню картинок пункт «Print on LX-D02» и ярлык в «Отправить».
Удаление: `.\windows\install.ps1 -Uninstall`. Журнал: `windows\print_server.log`.
### Советы
- **Бумага.** У драйвера по умолчанию лента 3276 мм. Пусть вас не пугает превью: сервер
срезает белое сверху и снизу и сжимает пустые промежутки до 6 мм. Для удобного превью
выберите в настройках печати размер 58×210.
- **Поля.** Ставьте поля «Минимальные» (в браузере) или 0 (в редакторах). Левое поле
сервер убирает сам, но правое срежет драйвер: печатная полоса 48 мм начинается
от левого края листа, а браузер с обычными полями сдвигает текст вправо.
- **Картинки** лучше печатать правым кликом, а не из браузера или «Фотографий»:
те раскладывают картинку по листу, и она выходит мелкой или обрезанной.
- Странный результат? Последнее задание сохраняется в `windows\last_job.bin`,
`python windows\print_server.py --test windows\last_job.bin` покажет его картинкой.
## Home Assistant
Нужен Bluetooth на хосте HA (встроенный адаптер или прокси ESPHome).
Скопируйте `custom_components/lxd02` в `/config/custom_components/`, добавьте
в `configuration.yaml` и перезапустите HA:
```yaml
lxd02:
mac: AA:BB:CC:DD:EE:FF
```
Действие:
```yaml
action: lxd02.print_text
data:
text: "Привет из Home Assistant"
size: 28 # размер шрифта, 10..120
density: 3 # плотность, 0..7
feed: 40 # прогон ленты после текста
separator: true # линия под текстом
```
Задания попадают в очередь на диске (`/config/.storage/lxd02`). Если принтер выключен,
вне зоны или занят телефоном, задание ждёт, и очередь печатается, как только HA снова
увидит принтер. Пока очередь не пуста, HA пробует раз в минуту. Действие не падает,
если принтера нет: ищите `LX-D02` в журнале HA.
### Пример: чат MeshCore на ленте
Печатает входящие сообщения канала `my_channel` из интеграции MeshCore: отправитель
и время, ниже текст, под ним черта. Свои исходящие пропускаются: интеграция присылает
их заново на каждое обновление статуса доставки.
```yaml
triggers:
- trigger: event
event_type: meshcore_message
event_data:
message_type: channel
conditions:
- condition: template
value_template: "{{ trigger.event.data.channel | lower == 'my_channel' and not trigger.event.data.outgoing | default(false) }}"
actions:
- action: lxd02.print_text
continue_on_error: true
data:
size: 24
separator: true
text: "{{ trigger.event.data.sender_name }} · {{ now().strftime('%d.%m %H:%M') }}\n{{ trigger.event.data.message }}"
mode: queued
max: 20
```
### Если Bluetooth на хосте зависает
На ТВ-приставках встроенный чип раз в несколько недель может зависнуть
(`hci0: Opcode 0x0c03 failed: -110` в `dmesg`, HA пишет `NotReady`). Помогает
перепривязать чип к драйверу. Пример сторожа на systemd для чипа на UART (`hci_uart_bcm`):
```sh
#!/bin/sh
# /usr/local/sbin/bt-watchdog.sh: run every minute by bt-watchdog.timer
D=/sys/bus/serial/drivers/hci_uart_bcm
up() { hciconfig hci0 2>/dev/null | grep -q "UP RUNNING"; }
up && exit 0
sleep 20 # HA/BlueZ may be resetting the adapter itself
up && exit 0
logger -t bt-watchdog "hci0 down, rebinding serial0-0"
echo serial0-0 > $D/unbind 2>/dev/null
sleep 2
echo serial0-0 > $D/bind
sleep 8
timeout 5 bluetoothctl power on >/dev/null 2>&1
```
Имена драйвера и устройства у вас могут отличаться: смотрите `/sys/bus/serial/drivers/`.
## Протокол коротко
| | |
|---|---|
| Имя в BLE-рекламе | `LX-D02` |
| Сервис / запись / уведомления | `FFE6` / `FFE1` / `FFE2` |
| Ширина печати | 384 точки, 48 байт на строку, по две строки в пакете из 96 байт |
| Рукопожатие | принтер шлёт вызов `5a0a`, ответ `5a0b` + байт CRC16 от MAC × 10 |
| Управление потоком | `5a08` пауза (буфер полон), `5a05` + номер строки (дослать с неё), `5a06` готово |
Весь протокол: `custom_components/lxd02/printer.py`, самопроверка `python printer.py`.
## Лицензия
MIT, см. `LICENSE`.