mmote.ru/content/posts/niimbot-proto/index.md
2024-07-29 22:31:06 +03:00

211 lines
13 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
title: Изучаем протокол принтеров Niimbot
description: Изучаем протокол принтеров Niimbot и печатаем этикетки, отправляя пакеты
date: 2024-06-29T21:10:28+03:00
draft: false
tags:
- bluetooth
- niimbot
- термопечать
- Niimbot B1
- Niimbot B110
- реверс-инжиниринг
- javascript
- typescript
- web
categories:
- reverse-engineering
featured_image: miniature.jpg
lastmod: 2024-07-29T22:30:57+03:00
telegram_entry_id: ""
type: default
---
После того, как поигрались с [niimprint](/niimbot-d110-pc), захотелось чего-то большего. У меня появилась идея написать полноценный веб-интерфейс для печати, в котором можно будет и рисовать этикетки, и печатать. Для этого я решил изучить протокол принтеров.
<!--more-->
> ⚠ **ПРЕДУПРЕЖДЕНИЕ**
>
> Данный проект предназначен только для ознакомительных и образовательных целей.
> Проект не связан с компанией Niimbot, не поддерживается ей и не предназначен
> для использования в коммерческих целях без согласия владельца.
## Структура пакета
Сейчас в моём владении два принтера D110 и B1.
Вооружившись Wireshark и Android телефоном, снял дампы обмена данными с принтером по bluetooth. Для этого нужно было включить опцию "Bluetooth HCI Snoop Log" в настройках разработчика, а потом на компьютере после печати запустить `adb bugreport <filename>`.
Изучив пакеты и сверившись с другими открытыми источниками, получилась такая структура пакета:
![niimbot packet](packet.png)
* **Prefix** префикс `0x03`, присутствующий только при одной команде - **Connect**.
* **Head** всегда 2 байта `0x55` `0x55`.
* **Command** ID команды (пакета).
* **Data length** количество байтов данных, идущих далее.
* **Data** непосредственно данные в количестве **Data length**.
* **Checksum** вычисляется с помощью XOR всех байтов от **Command** до последнего байта **Data**.
* **Tail** всегда 2 байта `0xAA` `0xAA`.
## Типы пакетов
На данный момент мне удалось идентифицировать следующие типы пакетов:
| ID команды | Наименование | ID ответа |
| ---------- | --------------------------- | ---------------------------------------------------------------------------------- |
| 0x01 | PrintStart | 0x02 |
| 0x03 | PageStart | 0x04 |
| 0x05 | PrinterLog | 0x06 |
| 0x0b | AntiFake | 0x0c |
| 0x13 | SetPageSize | 0x14 |
| 0x15 | PrintQuantity | 0x16 |
| 0x1a | RfidInfo | 0x1b |
| 0x1c | RfidInfo2 | 0x1d |
| 0x20 | PrintClear | 0x30 |
| 0x21 | SetDensity | 0x31 |
| 0x23 | SetLabelType | 0x33 |
| 0x27 | SetAutoShutdownTime | 0x37 |
| 0x28 | PrinterReset | 0x38 |
| 0x40 | PrinterInfo | 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e |
| 0x54 | RfidSuccessTimes | 0x64 |
| 0x58 | SoundSettings | 0x68 |
| 0x70 | GetVolumeLevel / WriteRFID | 0x71 |
| 0x83 | PrintBitmapRowIndexed | ⚠ Без ответа |
| 0x84 | PrintEmptyRow | ⚠ Без ответа |
| 0x85 | PrintBitmapRow | ⚠ Без ответа |
| 0x8e | LabelPositioningCalibration | 0x8f |
| 0xa3 | PrintStatus | 0xb3 |
| 0xa5 | PrinterStatusData | 0xb5 |
| 0xaf | PrinterConfig | 0xbf |
| 0xc1 | Connect | 0xc2 |
| 0xda | CancelPrint | 0xd0 |
| 0xdc | Heartbeat | 0xdd, 0xdf, 0xde, 0xd9 |
| 0xe3 | PageEnd | 0xe4 |
| 0xf3 | PrintEnd | 0xf4 |
Далее рассмотрим основные пакеты подобнее.
### Простой пакет
Данный тип пакета всегда содержит **0x01** в качестве данных.
```
| 1 |
| |
V1 |[ 1 ]|
```
Ответ:
```
| 1 |
| |
Ok |[ 1 ]|
Error |[ 0 ]|
```
### 0x01 PrintStart
Начало последовательности пакетов для печати. Формат данного пакета отличается в разных версиях протоколов. Варианты:
```
| 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
| | | | | | | | |
V1 |[ 1 ]| | | | | | | |
V3 |[ total_pages ]| | | | | | |
V4 |[ total_pages ]|[ 0 ]|[ 0 ]|[ 0 ]|[ 0 ]|[ page_color ]| |
V5 |[ total_pages ]|[ 0 ]|[ 0 ]|[ 0 ]|[ 0 ]|[ page_color ]|[ quality ]|
```
Значения:
* **total_pages** итоговое количество страниц.
* **page_color** цвет страницы (назначение неизвестно).
* **quality** вероятнее всего, плотность печати.
ID Ответа: **0x02**:
```
| 1 |
| |
Ok |[ 1 ]|
Error |[ 0 ]|
```
### 0xf3 PrintEnd
Начало данных страницы.
ID Ответа: **0xf4**.
[Простой пакет](#простой-пакет).
### 0x03 PageStart
Начало данных страницы. Вызывается между **PrintStart** и **PrintEnd**.
ID Ответа: **0x04**.
[Простой пакет](#простой-пакет).
### 0xe3 PageEnd
Конец данных страницы. Вызывается между **PrintStart** и **PrintEnd**.
ID Ответа: **0xe4**.
[Простой пакет](#простой-пакет).
## Важно знать
### Протокол обмена данным варьируется между разными моделями
В основном это касается набора пакетов при непосредственно печати. Изучив код приложения, можно сделать вывод, что есть пять вариаций протокола + вариации для самих моделей.
![proto files](proto_files.png)
### Вероятно, протокол обмена данными может варьироваться даже в пределах ревизий одной модели
Это можно увидеть в декомпилированном коде приложения:
![protocol tasks](protocol_tasks.png)
### Принтер искусственно занижает плотность печати при использовании неправильной или отсутствующей RFID метке
Вот тут довольно интересно. Принтеры и приложения ведут себя по разному.
**Метки нет вообще**:
* **Niimbot Android**
- Этикетку создать невозможно в принципе.
* **Niimbot Windows**
- D110 - не поддерживается, хоть и может печатать по usb.
- B1 - печатать можно, игнорируя предупреждения, плотность очень низкая.
* **Сторонние проекты**
- D110 - печатает без проблем с нужной плотностью.
- B1 - печатает с низкой плотностью.
**Метка есть, но от бумаги размером, который не поддерживается принтером**:
* **Niimbot Android**
- D110 - печатать можно, игнорируя предупреждения, плотность очень низкая (подлость самого приложения).
- B1 - аналогично с D110.
* **Niimbot Windows**
- D110 - печатать можно, игнорируя предупреждения, плотность очень низкая.
- B1 - печатать можно, игнорируя предупреждения, плотность очень низкая.
* **Сторонние проекты**
- D110 - печатает без проблем с нужной плотностью.
- B1 - аналогично с D110.
Что касаемо самой метки - считывание происходит при закрытии крышки.
Принтер видит метку даже если она снаружи корпуса. Так что можно просто приложить метку снаружи, закрыть корпус и печатать на чём попало.
## Источники
* [Тут](https://github.com/DelphiTeacher/OrangeFreeSDK/tree/master/%E7%B2%BE%E8%87%A3%E6%99%BA%E6%85%A7%E6%A0%87%E7%AD%BE%E6%89%93%E5%8D%B0%E6%9C%BAJCPrint/Client/JCPrintSDK) и [тут](https://github.com/dadrum/niimbot_flutter_plugin/tree/main/android/app/libs) нашлась библиотека jcprintersdk для Java. Библиотека обфусцирована.
* [kjy00302/niimprint](https://github.com/kjy00302/niimprint) - утилита на Python для печати изображений на принтерах niimbot.
* [AndBondStyle/niimprint](https://github.com/AndBondStyle/niimprint) - доработанный форк niimprint. Также благодаря автору и человеку с неизвестным мне ником удалось получить актуальную версию jcprintersdk с низкой обфускацией путём декомпиляции Android приложения.
* [ayufan/niimprint-web](https://github.com/ayufan/niimprint-web) - малофункциональный, но полезный проект печати через браузер.