- 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>
187 lines
9.7 KiB
Markdown
187 lines
9.7 KiB
Markdown
# 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`.
|