Pull to refresh

Используем старый рабочий сканер с пользой

Reading time 8 min
Views 24K
О чём расскажет этот пост?
О том, как использовать старый сканер вместе с сервером под Debian для сканирования при нажатии кнопки, да ещё и с автоматической сортировкой.

Зачем это нужно?
Это подходит всем, кого не устраивают завалы бумаги на рабочем месте. К примеру:
  • Школьникам и студентам, тем, у кого актуальна тема груды накопившихся раздаточных материалов в школе, техникуме или университете.
  • Служащим в офисе, у которых таких же бумаг и писем накапливается великое множество
  • Простым людям — для сканирования всяких приходящих писем, счетов и чеков (как известно, чеки имеют свойство выцветать, именно в этом и заключается моя проблема — нужно будет сканировать большое количество чеков, а это не особо удобно)


Как я себе это представляю?
Да очень просто. Положил бумажку в сканер, нажал кнопку, дождался звукового сигнала о конце сканирования и обработки, вынул бумажку, при необходимости — GOTO 10.

И что у меня вышло?
Бесперебойно работающая система, которая также позволяет пакетное сканирование и автоматическую сортировку по папкам, логирование и прочие заморочки — что в скрипт впишешь, то и будет.

Подарили мне как-то сканер Epson Perfection 1200U. Простой сканер, подключаемый по USB, довольно старый, но с хорошим разрешением. Захотелось мне подключить его к своему компьютеру — и тут засада, он рассчитан на 110 вольт. Ладно, позже достал трансформатор, подключил. Работает, но только под Windows XP — под Windows 7 драйверов нет и не предвидится. На рабочем компе Windows 7 x64 — и вот я, как дурак, запускал виртуалку каждый раз, когда нужно было что-то сосканировать, а другого сканера рядом не было.
*Место для горьких сожалений о недобросовестных производителях*

Шло время, рабочий компьютер поменялся по воле неосторожной кружки чая, мать её за ногу. Решил на новый компьютер поставить Debian, ибо привычнее. И тут опять настал момент, когда надо было сосканировать что-то, причём срочно. Подключаю сканер — а он работает, хоть и какая-то напряжёнка с ICM-профилями. Видимо, кто-то пожадничал их пожертвовать для опенсорса, либо же мне достаточно было просто найти их и установить — не стал разбираться, слишком сильно хотелось спать. Самое главное, что стало можно удобно сканировать что угодно. Стоп, а если подключить к серверу без GUI и запустить scanimage? Хм, работает. Класс!

Так, а на корпусе сканера есть кнопочка. Никогда ей не удавалось в Винде воспользоваться, ноль эмоций. Тут, впрочем, тоже. Запрос в Гугл нашёл два проекта — scanbuttond и scanbd. Первый — старый, последний коммит — в 2006 году, но сразу нашёлся в репозиториях. Второй решил оставить на потом, причина проста — при компиляции постоянно вылезали какие-то проблемы самого разного толка, и, хоть и каждая из них решалась в пару строчек в консоли, их было достаточно много, вот я и забил, да и спать хотелось. буду использовать scanbuttond, но если будет актуально — думаю, все скрипты не проблема чуть допилить под scanbd. Вопрос, конечно, в том, насколько не проблема… Но пока — scanbuttond.

Начало работы со scanbuttond

Ставлю scanbuttond из репозиториев, запускаю scanbuttond, смотрю в /var/log/daemon.log, нажимаю кнопочку, sleepbuttond радостно оповещает о том, что кнопочка нажата и затем отпущена. Прикольно!
А дальше что? Дальше всё просто. Первым делом отредактировать /etc/default/scanbuttond и включить запуск демона вместе с системой, ну и запустить его командой service scanbuttond start. Какие скрипты будут вызываться?
Первый — это initscanner.sh.example (переименовываем, граждане, не стесняемся, убираем этот .example), он вызывается каждый раз, когда подключается какой-либо сканер, и в основном (насколько я могу судить) является интерфейсом для подключения различных костылей, ну и иногда — оповещения и логирования.
Второй скрипт интереснее, он уже вызывается непосредственно тогда, когда нажимается кнопка. Называется он buttonpressed.sh.example, ну и последняя часть названия тут опять лишняя. Вызывается этот скрипт каждый раз, когда на кнопку нажимают. Именно в него и нужно совать все эти разные команды сканирования и прочее.
Что ж, сваял два скрипта для того, чтобы обрабатывать нажатие кнопки. Первый — на BASH. Когда нажимается кнопочка, scanbuttond передаёт управление этому скрипту, указывая номер кнопочки и название сканера как $1 и $2. Сканер один, кнопочка одна — мне на аргументы можно и не обращать внимания (всё равно спать хочется), но на потом запомнил. Первый скрипт — buttonpressed.sh — вызывает scanimage с заранее заданными параметрами, переносит готовое изображение в TIFF в папку в домашней директории, конвертирует tiff в jpg и и затем вызывает второй скрипт. Второй скрипт на Python подбирает изображению имя, исходя из занятых имён.
Первый скрипт - buttonpressed.sh
#!/bin/sh
#Большая часть скрипта у кого-то нагло спёрта
#Впрочем, по барабану, 

# daemon's name
DAEMON=scanbuttond

# securely create temporary file to avoid race condition attacks and to get some sleep
TMPFILE=`mktemp /tmp/$DAEMON.XXXXXX`

# lock file
LOCKFILE="/tmp/$DAEMON.lock"

# destination of the final image file (modify to match your setup)
DESTFOLDER="/home/user/Scans/"
DESTINATION=$DESTFOLDER"image.tiff"

# remove temporary file on abort
trap 'rm -f $TMPFILE' 0 1 15

# function: create lock file with scanbuttond's PID
mk_lock() {
	pidof $DAEMON > $LOCKFILE
}

# function: remove temporary and lock files
clean_up () {
	test -e $LOCKFILE && rm -f $LOCKFILE
	rm -f $TMPFILE
}

# function: check if lock file exists and print an error message using logger
chk_lock() {
	if [ -e $LOCKFILE ]; then
		#Another scanning operation in progress
		logger "scanbuttond: trying to start scanning operation while another is in progress "
		exit 1
	fi
}

# function: the actual scan command (modify to match your sleep)
scan() {
        #параметры для сканирования подобраны мной под мой сканер методом тыка для того, чтобы лучше сканировать чёрно-белые документы
	scanimage --format=tiff --resolution 300 --mode Gray --gamma-correction "High contrast printing" > $DESTINATION
        convert $DESTINATION $DESTFOLDER"image.jpg"
        logger "Filename: " `python /etc/scanbuttond/convert_scan.py`
        rm $DESTINATION
}

chk_lock
mk_lock
scan
clean_up


Второй скрипт - convert_scan.py
import os

filename = 'image.jpg'
directory = "/home/user/Scans"

os.chdir(directory)
try:
    filenames = [f for f in os.listdir(directory) if f.endswith('.jpg')]
except KeyError:
    filenames = []
counter = 1
new_filename = 'scan_000.jpg'
while new_filename in filenames:
    new_filename = 'scan_'+str(counter).zfill(3)+'.jpg'
    counter += 1
print new_filename
os.rename(filename, new_filename)


Для использования меняем переменную DESTFOLDER в первом скрипте и directory во втором.
Начал запускать это всё. Вручную запуск первого скрипта отрабатывает на ура. А вот если по кнопке — то фиг с маслом. Не сразу на сонную голову допёр, что дело в разрешениях, видимо, дело в том, что вывод скриптов никуда не показывался, а запустить scanbuttond в foreground и посмотреть вывод я догадался только к 5 утра. Короче, проблема в том, что в режиме демона все скрипты запускаются от пользователя saned, как и сам демон, в общем-то. Какие же шаги нужно предпринять?
Примем, что scanbuttond запускается от юзера saned, папка для хранения фотографий — /home/user/Scans, а доступ к папке нужно, помимо всего, иметь пользователю user.
usermod -aG saned user #добавляем user в уже существующую группу saned
chown -R user:saned /home/user/Scans #Назначаем владельцем папки группу saned
chmod -R 770 /home/user/Scans# Ставим нужные права на папку

Итог — сканер работает по кнопке, все фото кладёт в домашнюю директорию, только вот то, что нужно было сосканировать, так и не сосканировал. Короче, как всегда, вместо решения проблемы писал средство автоматического решения. Как всегда, хочется спать.

Но хочется-то большего!


А именно:
Автоматической сортировки сканов по директориям. Как я себе это представляю?

>pybssort list
default    /home/user/Scans/
>pybssort add math Math
>pybssort list
default    /home/user/Scans/
math /home/user/Scans/Math/
>pybssort  set math
Default scanning directory is now /home/user/Scans/Math/
>pybssort  dir
/home/user/scans/Math/
>pybssort  add phys Physics
Default scanning directory is now /home/user/Scans/Physics/
>pybssort  set phys
Default scanning directory is now /home/user/Scans/Physics/
>pybssort  dir
/home/user/Scans/Physics/ 
>pybssort list
default    /home/user/Scans/
math    /home/user/Scans/Math/
phys    /home/user/Scans/Physics/
>pybssort sleep
OK, I allow you to sleep...
No, wait, finish your article!
>pybssort del math
OK
>pybssort list
default    /home/user/Scans/
phys    /home/user/Scans/Physics/



Команды list, add, del, set предназначены для изменения папки сканирования. Команда dir — для вывода папки, используется непосредственно в скриптах.
В чём смысл?

В любой момент можно изменить папку для сканирования одной командой в консоли. Причём это может сделать любой пользователь — если такое нежелательно, нужно всего лишь изменить разрешения на папку с БД. Вы можете создавать контексты, смотреть их — и всё одной командой.
  • Сначала рассортировали бумаги в кучки на полу по темам, берём архивы с записями по физике.
  • В консоли набираем pybssort add phys Physics.
  • Кладём по листочку, нажимаем кнопку, дожидаемся конца сканирования, выкидываем сосканированный листочек и кладём следующий.
  • Все сканы — в папке /home/user/Scans/Physics.
  • Доходим до записей по математике, набираем pybssort add math Math, сканируем дальше — и все последующие сканы в /home/user/Scans/Math.
  • Нашли ещё листочек с записью по физике, набираем pybssort set phys — и опять всё летит в /home/user/Scans/Physics.


Хм, а как назвать эти вот все default, phys, math? Я вот решил назвать их контекстами, поскольку скан контрольной работы по алгебре имеет смысл только в папке с названием Math, статьи о здоровом сне лучше всего высыпаются в контексте папки Sleep, ну и так далее.

Что в итоге вышло?

Простая программа на Python. Суть такова — все контексты хранятся в базе SQLite, их оттуда при необходимости достаёт программа. Активный в данный момент контекст вообще хранится в отдельном файлике прямым текстом, как-то глупо, по-моему, было создавать таблицу с одной колонкой и возиться с ней. Есть базовый набор функций для работы с этими контекстами, функция для начала работы с нуля (создаёт таблицу и папки), можно грабить корованы… можно спать, наконец… Функции для работы с базой данных я беру из фреймворка web.py, на котором разрабатываю свои мелкие проектики.
Почему не использовать встроенный модуль sleeplite3 sqlite3? Почему я беру целый веб-фреймворк, чтобы взять из него только лишь web.database? Ответ прост — это банальная лень. Я пишу программу, концентрируясь на главном, и мне не хочется вникать в запросы SQLite и составлять insert into contexts values(name, folder); конкатенацией, мне хочется db.insert('contexts', name=name, folder=folder) и спать. Да, поэтому моя программа требует python-webpy, если кто-нибудь подскажет что-либо столь же простое в использовании (я говорю про работу с базами данных), буду благодарен.

Вот ссылка на программу:

gist.github.com/CRImier/7330722

Что нужно сделать для установки?
wget https://gist.github.com/CRImier/7330722/raw/pybssort.py
#Изменяем начальную директорию для сканирования
nano pybssort.py
chmod +x pybssort.py
mv pybssort.py /usr/local/bin/pybssort


Отмечу — её в первый раз нужно запускать от рута, поскольку это нужно для создания папок в /var/lib, чтобы хранить там базу данных. После первого запуска рут не требуется. Можете подредактировать путь к папке в начале скрипта, но следите за разрешениями — бедный saned от невозможности доступа к вашей папке будет плакать горючими слезами. Вы ведь не хотите его расстроить, верно?

А как её связать с уже существующими скриптами? Да просто в первом скрипте нужно вставить 'pybssort dir' вместо захардкоженной DESTINATION, а во второй скрипт ту же переменную передать аргументом командной строки.
Как-то так:

Первый скрипт
...

# destination of the final image file (modify to match your setup)
DESTFOLDER=`pybssort  dir`
DESTINATION=$DESTFOLDER"image.tiff"

...

# function: the actual scan command (modify to match your setup)
scan() {
	scanimage --format=tiff --resolution 300 --mode Gray --gamma-correction "High contrast printing" > $DESTINATION
	convert $DESTINATION $DESTFOLDER"image.jpg"
        logger "Filename: " `python /etc/scanbuttond/convert_scan.py $DESTFOLDER`
        rm $DESTINATION
}

...


Второй скрипт
import os
import sys #необходим для получения аргумента

...

directory = sys.argv[1]




Для дебага собственных скриптов советую выполнить в рабочем окружении следующую последовательность команд:
service scanbuttond stop
sudo -u saned scanbuttond -f &
tail -f /var/log/messages &
tail -f /var/log/daemon.log &

И использовать в скриптах echo, logger и print (для Python).

Критика насчёт всех трёх скриптов, исполнения, красоты кода, отступов, орфографии, оформления топика, bad practices в коде и логике, актуальности решения, возможных дополнений, адекватности автора и прочего приветствуется.

UPD:

Этот BASH-скрипт через какое-то время меня порядком достал — никак не мог вписать нормальную обработку ошибок. Плюнул, переписал скрипт на Питоне. Работает в итоге даже лучше. Из плюсов — обработка ошибок+нормальные логи, аудио оповещения и вроде как красивый код =) Доступен вот здесь. В ридми содержится информация по установке. У меня всё работает бесперебойно… Ну а если что-то не работает, прошу сообщать =)
Tags:
Hubs:
+21
Comments 15
Comments Comments 15

Articles