Онлайн-касса РИТЕЙЛ-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 с байтом ленты), и именно эти мелочи стоили большей части времени. Зато теперь под рукой есть карманный термопринтер, который умеет печатать погоду.