LINUX.ORG.RU

Canon LBP-2900 и другие CAPT-принтеры - ПИШУ ДРАЙВЕР

 ,


23

21

Попытался запустить LBP-2900 в Ubuntu. Довольно быстро выяснилось, что фирменный драйвер Canon - полный отстой, не поддерживается, и с этим надо что-то делать. Обнаружил также попытки написания энтузиастами аналогичного драйвера, но для 2900 он не работает. В связь с этим начал обратный инжениринг принтера и решил написать СОБСТВЕННЫЙ ДРАЙВЕР.

UPD: ТЕКУЩЕЕ СОСТОЯНИЕ

Исходники доступны на Github: https://github.com/agalakhov/captdriver

Чеклист к первому релизу:
[X] Передача параметров компрессии Hi-SCoA
[X] Компрессия Hi-SCoA
[X] Поддержка LBP-2900 и LBP-3000
[X] Баг «only 10 bytes»
[X] Печать многих страниц
[X] Ожидание наличия бумаги
[ ] Генерация PPD-файлов

Чеклист ко второму релизу:
[ ] Компрессия SCoA
[ ] Поддержка LBP-810 и LBP-1120

(Текст исходного верхнего поста следует)

Ищу единомышленников для Reverse Engineering протокола принтера. На сегодняшний день мне удалось полностью расшифровать протокол нижнего уровня USB и частично - протокол верхнего уровня. Мой драйвер уже может отсылать страницы на печать. ТРЕБУЕТСЯ расшифровать алгоритм сжатия пиксельных данных (он оказался отличным от алгоритма LBP-810 и, по-видимому, является какой-то модификацией ALPC-сжатия). Попытки прикрутить алгоритм от 810 привели к тому, что принтер включается и печатает, но на бумаге получаются только полосы, линии и регулярные узоры из пикселей. У меня пока нет времени на расшифровку, поэтому прошу помощи.

ОПИСАНИЕ ТОГО, ЧТО УДАЛОСЬ РАСШИФРОВАТЬ

Работать с принтером можно с помощью простого open(«/dev/usb/lp0») - libusb не требуется. Общение идет пакетами довольно простого формата. Формат пакета:

байты 1,2 - код команды - 16 бит (младший байт первый)

байты 3,4 - длина посылки (полная) - 16 бит (очевидно, меньше 4 байт не бывает)

байты с 5 - данные (опционально)

Если суммарная длина посылки превышает 4096 байт, посылка делится на части по 4096 байт.

Компьютер посылает принтеру команду. Принтер отвечает пакетом, содержащим код той же команды и минимум 2 байта данных (код возврата), всего не менее 6 байт. Эти 6 байт читают одним read(). Если длина превышает 6 байт, то затем делается read() на оставшуюся длину (она у меня никогда не превышала 4 килобайта, так что про ограничения ничего не знаю). Если не прочитать ответ принтера и продолжить посылать данные, он зависнет, и его придется выключить и включить снова.

Коды команд:

0xA1A1 - начало работы. Параметров нет (4 байта). Принтер отвечает длинной последовательностью байтов - видимо, номером модели, серийным номером, характеристиками и чем-то еще, я не разбирался.

0xA0A0 - какая-то проверка статуса? Встречается на 810, ни разу не видел на 2900. Параметров нет. Принтер отвечает длинной простышей байтов.

0xA0A8 - запрос какого-то статуса. Параметров нет. В коде возврата - явно битовые флаги.

0xA3A2 - что-то включает, меняет флаги в предыдущей команде. Параметров нет. Ответ всегда 0x0000.

0xE0A0 - проверка готовности. Если в ответе поднят бит 0x0008, то буфер принтера полон, надо ждать и не посылать больше данные.

0xA0A1 - проверка кучи вещей, в том числе наличия бумаги. Как оно работает на 2900 - не знаю.

0xA2A0 - загрузка первой магической последовательности. Параметр: магическая последовательность байтов.

0xE1A1 - загрузка второй магической последовательности.

0xE0A3, 0xE0A2, 0xE0A4 - что-то включают. Всегда идут в начале и в такой последовательности. Их отсутствие никак на печать не влияет(?). Возвращают 0, а при попытке вызвать повторно - 0x8800.

0xE0A5 - третья магическая последовательность.

0xD0A9 - загрузка магической последовательности, непосредственно предшествующая загрузке данных печати. Ответа на эту команду не дожидаются (?).

0xC0A0 - Главная Команда. Загружает в принтер сжатое изображение или его часть. Ответа принтера нет.

0xC0A4 - Конец Загрузки. Выдается сразу после 0xC0A0.

0xE0A7 - Включение Печати. Когда принтер подтвердит готовность после загрузки, выдают эту команду, и принтер начинает печатать. Параметр: 16-битное число 0x0001 (видимо, означающее «включить»).

Дополнительная информация - в исходниках драйвера http://www.boichat.ch/nicolas/capt/

Исходники того, что написал на данный момент, могу прислать.



Последнее исправление: cetjs2 (всего исправлений: 5)

Ответ на: комментарий от HighwayStar

в сусе после сборки с поддержкой libusb одинаково работает как cups 1.6.3 так и 1.5.4. Заносить usblp в блеклист необязательно, cups сам использует libusb когда он доступен.

HighwayStar ★★★★★
()
Ответ на: комментарий от HighwayStar

с моим принтером вот с таким кодом стабильная печать

Интересно. Это не ожидание ответа :) Это именно запрос бакэнду на посылку данных в принтер. Похоже, мы имеем дело с race conditions.

cupsSideChannelDoRequest(CUPS_SC_CMD_DRAIN_OUTPUT... - это flush(). Похоже, он блокирующий, если данных в буфере нет (т.е. если все уже ушло в принтер).

Добавь, пожалуйста, Signed-Off-By, чтобы в истории изменений патч показывался от правильного имени :)

Yampp
() автор топика
Ответ на: комментарий от Artificial_Thought

С текущей версией из гита ц меня он печатаете по крайней мере 4 страниц в одном задании (документе), но после завершения задания зависает тут

static void lbp2900_job_epilogue(struct printer_state_s *state)
{
	uint8_t buf[2] = { LO(state->ipage), HI(state->ipage) };
	capt_sendrecv(CAPT_JOB_END, buf, 2, NULL, 0);
	while (1) {
		if (capt_get_xstatus()->page_completed >= state->ipage)
			break;
		sleep(1);
	}
}

бесконечно продолжает опрашивать принтер

D [24/Sep/2013:01:07:52 +0900] [Job 119] CAPT: printer status P1=0 P2=0 B=0 B1=0 nE=1
D [24/Sep/2013:01:07:52 +0900] [Job 119] CAPT: pages 4/4/4/3
D [24/Sep/2013:01:07:52 +0900] [Job 119] CAPT: rastertocapt: end job
D [24/Sep/2013:01:07:52 +0900] [Job 119] CAPT: send  A9 E0 06 00 04 00
D [24/Sep/2013:01:07:52 +0900] [Job 119] Read 6 bytes of print data...
D [24/Sep/2013:01:07:52 +0900] [Job 119] CUPS_SC_CMD_DRAIN_OUTPUT received from driver...
D [24/Sep/2013:01:07:52 +0900] [Job 119] Wrote 6 bytes of print data...
D [24/Sep/2013:01:07:52 +0900] [Job 119] CAPT: waiting for 6 bytes
D [24/Sep/2013:01:07:52 +0900] [Job 119] Read 6 bytes of back-channel data...
D [24/Sep/2013:01:07:52 +0900] [Job 119] CAPT: recv  A9 E0 06 00 00 00
D [24/Sep/2013:01:07:52 +0900] [Job 119] CAPT: send  A0 E0 04 00
D [24/Sep/2013:01:07:52 +0900] [Job 119] Read 4 bytes of print data...
D [24/Sep/2013:01:07:52 +0900] [Job 119] CUPS_SC_CMD_DRAIN_OUTPUT received from driver...
D [24/Sep/2013:01:07:52 +0900] [Job 119] Wrote 4 bytes of print data...
D [24/Sep/2013:01:07:52 +0900] [Job 119] CAPT: waiting for 6 bytes
D [24/Sep/2013:01:07:52 +0900] [Job 119] Read 6 bytes of back-channel data...
D [24/Sep/2013:01:07:52 +0900] [Job 119] CAPT: recv  A0 E0 06 00 88 00
D [24/Sep/2013:01:07:53 +0900] [Job 119] CAPT: send  A0 E0 04 00
D [24/Sep/2013:01:07:53 +0900] [Job 119] Read 4 bytes of print data...
D [24/Sep/2013:01:07:53 +0900] [Job 119] CUPS_SC_CMD_DRAIN_OUTPUT received from driver...
D [24/Sep/2013:01:07:53 +0900] [Job 119] Wrote 4 bytes of print data...
D [24/Sep/2013:01:07:53 +0900] [Job 119] CAPT: waiting for 6 bytes
HighwayStar ★★★★★
()
Ответ на: комментарий от HighwayStar

два быстрых варианта починки этой проблемы

diff --git a/src/prn_lbp2900.c b/src/prn_lbp2900.c
index d2e821a..ac97ac8 100644
--- a/src/prn_lbp2900.c
+++ b/src/prn_lbp2900.c
@@ -152,7 +152,7 @@ static void lbp2900_job_epilogue(struct printer_state_s *state)
        uint8_t buf[2] = { LO(state->ipage), HI(state->ipage) };
        capt_sendrecv(CAPT_JOB_END, buf, 2, NULL, 0);
        while (1) {
-               if (capt_get_xstatus()->page_completed >= state->ipage)
+               if (capt_get_xstatus()->page_completed >= state->ipage - 1)
                        break;
                sleep(1);
        }

diff --git a/src/prn_lbp2900.c b/src/prn_lbp2900.c
index d2e821a..14b6160 100644
--- a/src/prn_lbp2900.c
+++ b/src/prn_lbp2900.c
@@ -152,7 +152,7 @@ static void lbp2900_job_epilogue(struct printer_state_s *state)
        uint8_t buf[2] = { LO(state->ipage), HI(state->ipage) };
        capt_sendrecv(CAPT_JOB_END, buf, 2, NULL, 0);
        while (1) {
-               if (capt_get_xstatus()->page_completed >= state->ipage)
+               if (capt_get_xstatus()->page_out >= state->ipage)
                        break;
                sleep(1);
        }

второй вроде получше, хотя может ошибка может где-то не тут

HighwayStar ★★★★★
()
Ответ на: комментарий от HighwayStar

Чего он ждет - понятно:

CAPT: pages 4/4/4/3

Думает, что напечатал только три страницы. Это ответ принтера такой. (Какого черта? По идее, четвертое число - это как раз количество выданных листов)

Попробуй поменять местами цикл и команду CAPT_JOB_END - решает проблему? Если нет - поменяй page_completed на page_printing, это точно поможет, хотя и неправильно по сути.

Можно еще так:

while (1) {
	const struct capt_status_s *status = capt_get_xstatus();
	if (! FLAG(status, CAPT_FL_PROCESSING1) && ! FLAG(status, CAPT_FL_PRINTING)
		break;
	sleep(1);
}

Yampp
() автор топика
Ответ на: комментарий от HighwayStar

второй вроде получше, хотя может ошибка может где-то не тут

Второй верный, но он равноценен просто вырезанию этого цикла (ибо такое же ожидание делается в конце страницы). Первый - точно неверный.

Я еще подозреваю, что CAPT_JOB_END чему-то мешает, можно попробовать ее подвинуть ниже, после цикла.

(Вот такой это поганый принтер - вчера у меня все прекрасно работало...)

Yampp
() автор топика
Ответ на: комментарий от Itaris

Снифф с USB сделал, куда его закинуть?

Куда удобнее, лишь бы я забрать мог. Можно на Яндекс.Диск, например. Можно мне на почту (адрес в исходниках есть).

Кое-что я уже по фирменному драйверу посмотрел. В общем, ничего неожиданного, протокол в целом тот же, что и у черно-белых принтеров. Но повозиться с отладкой скорее всего придется.

Yampp
() автор топика
Последнее исправление: Yampp (всего исправлений: 1)
Ответ на: комментарий от Yampp

да, если передвинуть CAPT_JOB_END вниз после цикла, то отлично работает

D [24/Sep/2013:10:19:39 +0900] [Job 131] CAPT: pages 4/4/4/4
D [24/Sep/2013:10:19:39 +0900] [Job 131] CAPT: send  A9 E0 06 00 04 00
D [24/Sep/2013:10:19:39 +0900] [Job 131] Read 6 bytes of print data...
D [24/Sep/2013:10:19:39 +0900] [Job 131] CUPS_SC_CMD_DRAIN_OUTPUT received from driver...
D [24/Sep/2013:10:19:39 +0900] [Job 131] Wrote 6 bytes of print data...
D [24/Sep/2013:10:19:39 +0900] [Job 131] CAPT: waiting for 6 bytes
D [24/Sep/2013:10:19:39 +0900] [Job 131] Read 6 bytes of back-channel data...
D [24/Sep/2013:10:19:39 +0900] [Job 131] CAPT: recv  A9 E0 06 00 00 00
D [24/Sep/2013:10:19:39 +0900] [Job 131] CAPT: rastertocapt finished
D [24/Sep/2013:10:19:39 +0900] [Job 131] Sent 92258 bytes...
D [24/Sep/2013:10:19:39 +0900] [Job 131] Waiting for read thread to exit...
D [24/Sep/2013:10:19:39 +0900] PID 2828 (/usr/lib/cups/filter/rastertocapt) exited with no errors.
D [24/Sep/2013:10:19:46 +0900] [Job 131] Read thread still active, aborting the pending read...
D [24/Sep/2013:10:19:47 +0900] [Job 131] Resetting printer.
D [24/Sep/2013:10:19:47 +0900] PID 2829 (/usr/lib/cups/backend/usb) exited with no errors.

HighwayStar ★★★★★
()
Ответ на: комментарий от Yampp

Как ни странно, печатает, но между страницами принтер иногда останавливается и заново перезапускается, ну и после окончания печати оно дохнет с «CAPT: no reply from backend»

Artificial_Thought ★★★★
()
Ответ на: комментарий от Artificial_Thought

печать все еще работает, пробовал на том же 4х страничном документе

Повезло. Сейчас должен и наличия бумаги уметь ждать - если бумаги нет, ждет нажатия кнопки.

Я этот код вообще как написал, так и не проверил (доступа к принтеру пока нет). Даже запускать не пытался. Надеялся чисто на свой опыт, как-никак уже 15 лет программирую. Но решил рискнуть ради скорости разработки.

но между страницами принтер иногда останавливается и заново перезапускается

Можно лог этого момента? И лог самого конца печати перед подыханием? Я думал, мы этот баг побороли.

Хотя это может быть просто подтормаживание cups - не успевает отрендерить очередную страничку.

Yampp
() автор топика
Ответ на: комментарий от Yampp

Повезло. Сейчас должен и наличия бумаги уметь ждать - если бумаги нет, ждет нажатия кнопки.

у меня отсутствие бумаги не определяет судя по логу, кнопкой не мигает. вечером посмотрю подробнее

HighwayStar ★★★★★
()
Ответ на: комментарий от Yampp

отсутствие бумаги в логе определяется и нажатие кнопки тоже, но после нажатия кнопки принтер не проверяет физическое наличие бумаги, а сразу возвращает no paper http://paste.org.ru/?288nb7

HighwayStar ★★★★★
()
Ответ на: комментарий от HighwayStar

вот лог в винде от состояния «нет бумаги», затем нажата кнопка и печать продолжена http://rghost.ru/48963508

там после нажатия кнопки посылаются команды

CAPT_CHKXSTATUS = 0xA0A8,
CAPT_CHKJOBSTAT = 0xA0A1,

????  = 0xE1A1,

CAPT_START_1    = 0xE0A3,
CAPT_START_2    = 0xE0A2,
CAPT_START_3    = 0xE0A4,

...

CAPT_SET_PARMS  = 0xD0A9,
CAPT_PRINT_DATA     = 0xC0A0

HighwayStar ★★★★★
()
Ответ на: комментарий от HighwayStar

в предудыщем сообщении перепутал. ???? = 0xE1A1, читать как

A2 E1 10 00 00 00 00 00 00 00 00 00 00 00 00 00 ,16

еще есть интересная команда

A6 E0 06 00 00 00 ,6

она тоже встречается при обработки ситуации «нет бумаги» в винде, может быть она связана с миганием лампочки

HighwayStar ★★★★★
()
Последнее исправление: HighwayStar (всего исправлений: 2)
Ответ на: комментарий от HighwayStar

Ага, спасибо. Про команды CAPT_START_* я и сам вспомнил, что что-то такое тут надо... эту часть надо аккуратненько перенести в page_prologue. А вот лампочка - это интересно. Я так понимаю, ею можно мигать когда угодно.

A2 E1 - это какое-то управление состоянием. Вот про нее выдержка из SPECS в capt3:

a2 e1: data sent depends on state:
    init : 13 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 (comes after a0 a2)
    cancel: 31 00 01 02 01 00 00 00 00 00 01 00 00 00 00 00 (comes after a5 e0)

Yampp
() автор топика
Ответ на: комментарий от HighwayStar

Спасибо.

A6 E0 скорее всего непосредственно лампочкой управляет, т.е отдельно команды «зажечь» и «погасить». Вообще лампочку найти будет безумно трудно - это всего лишь один бит, иголка в стогу сена.

Что делает A2 E1, черт ее знает. Без нее все прекрасно работает.

Я начинаю понимать суть протокола принтера. Он рассчитан на СЕТЬ. Большая часть полей в нем - не для принтера, а для других клиентов, чтобы они могли посмотреть, занят принтер или свободен, кто что печатает, сколько ему еще осталось и т.д. Принтер нормально работает и без этой ерунды, а общий доступ CUPS и сам умеет делать.

Yampp
() автор топика
Ответ на: комментарий от Yampp

А неужели виндовый сервис печати всё это не умеет разруливать? Хотя, тогда это объясняет, зачем для печати с удалённого компьютера нужен драйвер принтера.

Artificial_Thought ★★★★
()
Ответ на: комментарий от Yampp

если искать побитовые отличия, то вот лог запуска на печать документа без бумаги в винде до мигания лампочки http://paste.org.ru/?m77anq

интерес представляют записи номер 17, 19 и 221,223

17  A0 A2 0C 00 *00* 00 1E 00 00 00 00 00 ,12
221 A0 A2 0C 00 *02* 00 1E 00 00 00 00 00 ,12
19   A1 E1 A6 00 00 00 00 00 01 00 00 00 1E 00 08 00 34 00 00 00 *01* 01 02 00 E4 FD E4 FD DD 07 09 19 06 22 34 00 00 00 00...
223  A1 E1 A6 00 00 00 00 00 01 00 00 00 1E 00 08 00 34 00 00 00 *02* 01 02 00 E4 FD E4 FD DD 07 09 19 06 22 34 00 00 00 00...

в драйвере сейчас эти биты захардкожены в magic последовательностях

HighwayStar ★★★★★
()
Ответ на: комментарий от Artificial_Thought

А неужели виндовый сервис печати всё это не умеет разруливать?

Умеет так плохо, что Canon предпочли изобрести свой велосипед.

а посмотрел уже, по спекам capt 3 это поля для jj jj — job number

Да. Надо бы переделать нормально, но не в приоритете. Судя по всему, этот job number принтером вообще нигде не используется, а тупо возвращается обратно драйверу в ответах статуса. То же самое и к числу страниц в задании относится: я там просто отсылаю всегда 1 (в cups неудобно смотреть число страниц в начале работы), и принтеру явно пофиг.

Yampp
() автор топика

Попробовал на LBP2900. При запуске печати ничего не происходит, из cups'а удалось выудить такую ошибку:

CAPT: unknown printer MFG:Canon;MDL:LBP2900;CMD:CAPT;VER:2.1;CLS:PRINTER;DES:Canon LBP2900'"

В диалоге запуска печати пишет «Processing page 1...»

ILLIOS
()
Ответ на: комментарий от ILLIOS

CAPT: unknown printer...

Каким компилятором собран драйвер? Это может быть, если компилятор игнорирует __attribute__((constructor)), который используется для подключения «внутренних» драйверов.

Технические детали: «внутренние» драйверы образуют связный список, который заполняется регистрирующей функцией до вызова main(). Вызов регистрации делается в prn_XXXX.c с помощью макроса, содержащего __attribute__((constructor)). Нормальную работу этой системы может нарушить использование экзотических компиляторов или линкеров. GNU ld, gold, gcc и clang в актуальных версиях полностью поддержаны, про другие компиляторы и старые версии ничего сказать не могу.

Прошу прощения за вынужденную задержку в разработке. Лечим ребенка.

Yampp
() автор топика

Круто, что драйвер продолжает своё развитие! Помню как-то давно даже собрал его и даже что-то распечатал. Правда, потом всё свелось к установке виртуалки с XP и печати через неё, а затем принтер был благополучно оставлен при переезде в другой город.

Удачи и успехов в этом нелёгком деле.

jekader ★★★★★
()
Ответ на: комментарий от hypuson

Не за что! Сейчас в разработке вынужденный перерыв, много всего навалилось. Потом обязательно продолжу.

Сборка сейчас не автоматизирована «специально». Я не хочу, чтобы сырая версия получила излишнее распространение. Можете собрать - пользуйтесь на здоровье, не можете - подождите хотя бы бета-версии. Поймите правильно, я не могу позволить себе роскошь поддерживать незаконченную программу.

Yampp
() автор топика
Ответ на: комментарий от Yampp

Поменял в исходнике - печать заработала, но:

1. Из либрофиса печатает
2. Некоторые пдф печатает, некоторые нет. Причем если оно не смогло напечатать, то в следующий раз будет что-то печатать только после перезапуска принтера

Вот лог ошибок:
https://dl.dropboxusercontent.com/u/3706782/error_log_2.tar.bz2

ILLIOS
()
Ответ на: комментарий от Yampp

Есть одна вещь, которая мне не понравилась в логе: как будто счетчики страниц в принтере не обнуляются при новом джобе. Странно. Надо код инициализации перепроверить.

Yampp
() автор топика