evgen@spb: ~/2026/09/onlajn-kassa-ritejl-01fm-kak-termoprinter-protokol-shtrih-m/

Онлайн-касса РИТЕЙЛ-01ФМ как термопринтер: разбор протокола ШТРИХ-М

Была задача чисто на «поиграть»: к Raspberry Pi по USB подключена онлайн-касса РИТЕЙЛ-01ФМ, и захотелось использовать её просто как чековый принтер. Печатать что угодно: строку из терминала, погоду, тестовый чек. Пятиминутное дело, как мне казалось. На деле пришлось раскопать, как касса вообще подключается, поднять сеть, разобрать протокол ШТРИХ-М и по дороге вылечить аппаратную ошибку. Ниже весь путь по шагам.

Касса это не принтер и не COM-порт

Первое, что ломает шаблон. Обычный чековый принтер виден в системе как /dev/usblp0 или как виртуальный COM-порт (/dev/ttyUSB*, /dev/ttyACM*). Смотрю на Pi:

lsusb
...
Bus 001 Device 005: ID 18d1:4ee4 Google Inc. Nexus/Pixel Device (tether+ debug)

Ни /dev/ttyUSB*, ни /dev/usb/lp*. Зато в dmesg виден rndis_host и появился интерфейс eth1. То есть касса представляется системе как USB-RNDIS, обычная сетевая карта. Производитель в дескрипторе - NXP, serial SHM001. Печатать на неё надо не через устройство, а по TCP.

Ищем адрес кассы

Своего DHCP касса не раздаёт, dhcpcd на её интерфейсе уходит в IPv4LL (169.254.x.x). Значит у кассы фиксированный адрес из какой-то заводской подсети, и его надо найти. nmap на Pi не оказалось, поэтому написал короткий ARP-сканер на голых сокетах, который проходит по типовым подсетям (192.168.137.0/24, 192.168.0.0/24 и так далее):

FOUND 192.168.137.111 00:60:37:c2:b8:9e

Касса на 192.168.137.111, подсеть 192.168.137.0/24 - привет, Windows ICS, видимо, так её настраивали изначально. Поднимаю на интерфейсе адрес из этой же подсети:

ip addr add 192.168.137.1/24 dev eth1

Пинг идёт. Сканирую порты - открыт ровно один, 7778. Это управляющий порт ШТРИХ-М поверх TCP.

Протокол ШТРИХ-М в двух словах

Протокол простой и старый. Кадр выглядит так:

STX | LEN | DATA | LRC

Управление через байты ENQ (0x05), ACK (0x06), NAK (0x15), а контрольная сумма LRC - это XOR всех байтов данных. Первый байт в DATA - код команды, дальше параметры. Большинство команд требуют пароль оператора (4 байта, little-endian).

Первым делом беспарольная команда 0xFC «Получить тип устройства»:

name: РИТЕЙЛ-01ФМ

Связь есть, касса отвечает. Но дальше начинается самое интересное.

Ошибка 116, которая притворялась неверным паролем

Любая парольная команда (короткий статус 0x10, печать строки 0x17) возвращает код ошибки 116 (0x74). Я сначала уверенно решил, что это неверный пароль, и честно перебрал все пароли от 0 до 30. Все дают 116. Тут стоило остановиться раньше: если ошибка одна и та же на любой пароль, дело не в пароле.

Полез в документацию по кодам ошибок именно для Retail-01Ф. И вот оно:

0x74 (116) = Ошибка ОЗУ

Не пароль. Это состояние, в котором касса теряет содержимое энергонезависимой памяти: настройки, счётчики, состояние смены. Поэтому 0xFC (читает статику из прошивки) работает, а всё, что лезет в ОЗУ, отбивается. Классическая причина - севшая батарейка на плате, которая это ОЗУ и питает. Так и оказалось: батарейка дохлая.

Урок на будущее: не додумывать смысл кода ошибки по памяти, а лезть в таблицу под конкретную модель. Полчаса возни с паролями были ровно из-за этого.

Технологическое обнуление, у которого нет пароля

Лечится «Ошибка ОЗУ» технологическим обнулением: касса переинициализирует ОЗУ, настройки сбрасываются к заводским, данные фискального накопителя при этом не трогаются. Команда - 0x16.

По старой спецификации 0x16 это [пароль 4 байта]. Отправляю, получаю 0x7E (126) - «неверное значение в поле длины». То есть формат не тот. Перебираю варианты длины и нахожу: в прошивке этой кассы (протокол v2.0) команда 0x16 идёт вообще без пароля, только код команды:

# было (старый протокол): 0x16 + пароль(4)  -> ошибка длины
# стало (v2.0):           0x16              -> err=0, обнуление прошло

После обнуления статус 0x10 наконец отвечает err=0. Касса ожила. Ставлю дату и время (команды 0x21, 0x22 и подтверждение 0x23) и иду печатать.

Формат печати строки

Команда печати строки - 0x17. По классике это [пароль 4][текст]. Отправляю - снова ошибка, теперь 0x33 (51), «некорректные параметры в команде». Опять расхождение с новым протоколом. Перебор показал рабочий формат:

0x17 | пароль(4) | флаг ленты(1) | текст(cp1251)

Ключевой момент - байт флага ленты между паролем и текстом (2 = чековая лента, 1 = журнал, 3 = обе). Без него прошивка команду не принимает. И ещё: кодировка текста - cp1251, а не cp866. Это было видно ещё по ответу 0xFC, где имя «РИТЕЙЛ-01ФМ» пришло именно в cp1251.

Отправляю строку с флагом 2 - из-под крышки вылезает чек с текстом. Работает.

DNS на Pi, попутная история

Когда добрался до печати погоды, выяснилось, что у самого Pi сломан DNS: /etc/resolv.conf пустой, имена не резолвятся. Интернет по IP частично есть (1.1.1.1 пингуется, 8.8.8.8 в этой сети закрыт). Прописал резолвер и закрепил его так, чтобы он переживал обновление DHCP-аренды:

echo "nameserver 1.1.1.1" >> /etc/resolv.conf.tail

После этого запрос погоды через open-meteo (без ключа, по координатам) заработал.

Что получилось в итоге

Собрал один скрипт /usr/local/bin/kassa с подкомандами:

kassa up            поднять сеть + техобнуление (после обесточивания)
kassa net           поднять сеть на USB-интерфейсе кассы
kassa reset         техобнуление + дата/время
kassa status        модель, режим, состояние
kassa print [текст] печать строки или из stdin
kassa test          демо-чек
kassa weather       погода с пиктограммой

Погода печатается с ASCII-пиктограммой по состоянию (псевдографики в cp1251 нет, поэтому рисую обычными символами): ясно - солнце, пасмурно - облако, дальше дождь, снег, гроза, туман. Пример чека:

================================
            ПОГОДА
        2026-09-19 16:00
================================
           .--.
         .(    ).
        (___.__)__)
           Пасмурно
--------------------------------
 Температура:            15.2°C
 Ощущается:              14.0°C
 Влажность:                78%
 Ветер:            3.2 м/с СЗ
 Давление:         754 мм рт.ст.
================================

Что стоит помнить

Самый неприятный нюанс - батарейка. Пока она дохлая, «Ошибка ОЗУ» возвращается после любого снятия питания, и техобнуление приходится повторять. Порядок восстановления такой: kassa net (если Pi перезагружался), потом kassa reset, потом печать. Всё это зашито в kassa up. Софтом это не лечится - помогает только замена CR2032 на плате.

По протоколу главный вывод простой: не доверять коду ошибки по памяти и не считать, что структура команды совпадает со старой спецификацией. Прошивка v2.0 у РИТЕЙЛ отличается по мелочам (0x16 без пароля, 0x17 с байтом ленты), и именно эти мелочи стоили большей части времени. Зато теперь под рукой есть карманный термопринтер, который умеет печатать погоду.

evgen@spb: ~/contacts
evgen@spbping -c1 evgen.info
64 bytes from evgen.info: отвечу в течение рабочего дня
evgen@spbcat contacts.txt
mail      ya@evgen.info
telegram  @EvgenOne
github    github.com/onegin
blog      evgen.info/blog
evgen@spbecho $ЗАЧЕМ_ПИСАТЬ
интересная инфраструктурная задача · вопрос по Proxmox или Icinga ·
желание обсудить, почему memtest молчал, а память была битая
evgen@spb
© 2026 Евгений Подолинский · Санкт-Петербург
синий здесь – не тот, о котором вы подумали.