TWAIN 2: Д'Артаньян 20 лет спустя

TLDR; Сохацкий опять за неделю ебанул нетленочку.

Как вы знаете в 2001 году первоей моей работой в международной компании на 3-м курсе КПИ была корпорация International Land Systems, Inc., где мы писали системы сканирования, хранения и управления бизнес-процессами для автоматизации документооборота в кадастровом деле для таких стран как Ямайка, Египет, Азербайджан (на уровне министерств). Также вы можете знать со страниц этого блога, что делалось это на самописном LISP для (тогда еще) .NET Framework 1.0.

И вот, спустя 20 лет, оказывается ситуация на рынке совсем не изменилась. Сканеры по-прежнему не умеют сканировать в браузеры, поэтому такие производители как LeadTools (библиотеку которых я использовал тогда) или Dynamsoft (которую мы пытались использовать сейчас) предлагают свои решения для обеспечения доставки отсканированых изображений в контекст веб-браузера посредством промежуточного локального веб-сервера имеющего доступ к физическому устройству TWAIN. И решения эти, надо сказать, слишком дорогие для производителей СЭД на рынке Украины, где выставлена более-менее унифицированая цена СЭД в около тыщи долларов за год, а стоимость решений для сканирования начинается только в пол цены от этого! Поэтому все они должны использовать свои решения для сканирования.

Наш продукт "МІА: Документообіг" состоит из трех суб-продуктов: 1) "МІА: CRM" (ядро CRM системы, на базе которого построена СЭД), 2) "МІА: Офіс" — система редактирования офисных документов Word, Excel, PowerPoint (OpenXML SDK) для браузера, 3) "МІА: Сканування" — система захвата изображений из сканера в браузер, а также приложение в области уведомлений для получения документов с бекендов, и именно о нем пойдет речь в этой статье.

Формирование требований и компоненты архитектуры

Предварительные исследования показали, что все заказчики Украины на рынке СЭД хотят трей приложение для трех систем (Mac, Linux, Windows), которое бы одновременно: 1) выступало и локальным сервером для браузера, который имеет доступ к цифровым ключам, сканерам, и другим устройствам, и обслуживает запросы со страницы приложения "МІА: Документообіг" и 2) было MQTT клиентом, соединялось с брокером сообщений и подписывалось для получения различных оповещений реального времени по заданным каналам подписок. Для реализации первой части нами в январе был написан F# (потому, что мы уже не дети, чтобы писать на LISP) WebSocket сервер, лайки которому ставил Филипп Картер, когда еще был програмным менеджером F# в Microsoft. Вторая часть просто использует NuGet библиотеку System.Net.Mqtt и конектится к брокеру EMQ X, который является опциональной частью поставки "МІА: Документообіг".

Также основным приоритетным требованием кроме кросслпатформенности является получение сразу PDF документов, поэтому в поставку была включена достаточно небольшая библиотека PDFSharp (700KB).

* Kodak Alaris C# TWAIN SDK (256KB)
* Empira C# PDF SDK (700KB)
* INFOTECH F# WebSocket server (64KB)
* Xamarin C# MQTT client (160KB)

Что касается самого главного, TWAIN SDK, то тут сразу есть два варианта: писать все самому на С++ (дорого) или писать все самому на .NET (дешево). Сейчас официальный TWAIN SDK который лежит у них на Github написано компанией KODAK Alaris, поэтому я взял их примеры и начал работать. Структура их примеров содержит три проекта, в каждом из которых содержится имплементация одного из режимов работы сканера: 1) Memory Transfer; 2) Native Transfer; 3) File Transfer. Но ни один из этих проектов не содержит код который можно использовать прямо, однако там достаточно того, чтобы понять что происходит.

Я сразу начал с Memory режима и сразу получил стабильные артефакты при неопределенных обстоятельствах. Потратив день на эту проблему, я быстро переключился на Native режим и еще за один день полностью переписал сканирование на него, добавив дуплексное сканирование и автоподачу с фидера (автосканирование). Получив прототип стабильного сканирования я создал прослойку из хелперов для достаточно наивного студенческого Kodak Alaris SDK. Я пытался получить Single File Executable, но ни ILMerge ни современные средства не позволяют это сделать для ассемблей скомпилированых F# компилятором (содержащих FSharp.Core). Зато я добился того, что для того, чтобы собрать приложение не нужно вообще ничего кроме Windows:

> C:\Windows\Microsoft.NET\Framework\v4.0.30319\MSBuild.exe mia-agent.csproj

Таким образом, мы гарантируем, что в наших экзешниках нет никаких "петь" или блокчейновских ренсомварей, что особенно критично для наших заказчиков. Поэтому любой недоверчивый пользователь может собрать "МІА: Сканування" самостоятельно не используя вообще никаких средств разработки!

Благодаря тому, что мы выставили целевую платформу как .NET Framework 4.6 наш продукт "МІА: Сканування" может запускаться на любом виндовсе который младше 10 мая 2015 года, и при этом не требует установки никаких зависимостей (ни VC рантаймов ни .NET фреймворков)! Реально эта фича может сделать этот продукт народным.

TWAIN протокол

Теперь пару слов для тех, кто будет программировать ядро сканирования. Ядро сканирования состоит из функции ScanCallbackNative, которая будучи вызвана захватывет поток ОС пока полностью не отсканирует сесиию. Если вы выставили AUTOSCAN капабилити перед сканированием, то это значит, что вы после каждого изображения должны сами сново себя вызывать для продолжения сканирования пока количество страниц в фидере будет возвращаться -1. Как только оно станет 0 — это обозначает последнюю страницу сканирования. Имея последнюю и запомнив первую страницу (которая повернула колесо сканирования) мы таким образом получаем диапазон страниц (набор изображений), которые нам необходимо склеить для формирования многостраничного PDF или TIFF файла.

Важной частью протокола является функция Rollback которая делает режимный спуск с максимального (7) до минимального режима (2). Логика этой работы описана на странице 3-27 TWAIN спецификации 2.4 от марта 2017.

Библиотека INFOTECH TWAINI организована таким образом, что если ее правильно грепнуть, то вы получите минимальный набор для сканирования с ремарками из какого в какой режим функция переводит контекст сканера:

$ cat TWAINI.cs | grep TWAIN: // TWAIN: 7 -> 2: Rollback: Page 3-27 of 2017-03-TWAIN-SPEC-2.4 // TWAIN: 4 -> 4: NativeTransfer: CONTROL/CAPABILITY/SET // TWAIN: 4 -> 4: AutoFeed: CONTROL/CAPABILITY/SET // TWAIN: 4 -> 4: AutoScan: CONTROL/CAPABILITY/SET // TWAIN: 4 -> 4: EnableDuplex: CONTROL/CAPABILITY/SET // TWAIN: 4 -> 4: ProgressDriverUI: CONTROL/CAPABILITY/SET // TWAIN: 2 -> 3: OpenManager: CONTROL/PARENT/OPENDSM // TWAIN: 2 -> 2: GetDefault: CONTROL/IDENTITY/GETDEFAULT // TWAIN: 3 -> 3: GetDataSources: CONTROL/IDENTITY/GETFIRST,GETNEXT // TWAIN: 3 -> 4: OpenScanner: CONTROL/IDENTITY/OPENDS // TWAIN: 6 -> 6: StopFeeder: CONTROL/PENDINGXFERS/STOPFEEDER

Если вы хотите реально более менее детально изучить протокол, то вам конечно же нужно выучить наизусть тест сьют набор для прохождения TWAIN сертификации, а если вы (как я) немного туповат и с СДВГ, то вам лучше воспользоваться трейсером, который покажет вам не только все фунции и их параметры, но и правильные последовательности в которых это все нужно вызывать. Тут нужно сказать что TWAIN сканеры программируются с использованием CSV протокола. Я лично без шуток считаю это лучшим протоколом для энтерпрайза. Спасибо TWAIN, что не JSON, благодаря CSV эта статья как и код выглядят красивыми.

Windows Forms

Как вы знаете для .NET можно делать приложения без XAML (дешево). Во времена до XAML были просто Windows Forms. Потом, начиная с Vista появился WPF и XAML приложения (дорогой способ выпуска продуктов, но зато с 3D-анимациями и их моделями в Adobe Premiere), которые до сих пор являются главным способом построения приложений наряду с COM/DCOM. К счастью политика Microsoft смягчилась и в Windows Store теперь будут пускать и старые дедовские Win32 приложения в том числе и Windows Forms. Поверьте, вы не хотите выводить на рынок WPF приложение если вы не финансируетесь нефтью напрямую.

Приложение состоит из трех форм: FormScan (главная форма сканирования с отображением страниц сканирования), FormSetup (настройки профилей сканирования), FormSelect (выбор источника сканирования). Форма FormScan сделана главной, она использует активный инстанс SystemTrayIcon из которого берется иконка в нотификации. А вторая трей иконка используется как NotifyIcon для динамического рендеринга числа непрочитанных сообщений в инбоксе пользователя. Потому что если наоброт то в нотификациях будут сыпаться иконки с цифрами, хотелось бы этого избежать.

Форма FormSetup управляет настройками сканера. Сканеры устроены таким обрзом, что предлагают вам всегда свое окно, которое не только позволяет вам настроить как вы будете сейчас сканировать, но и получить сериализированный слепок этих настроек с гарантированным отсутствием семантики (не пытайтесь это расшировать). Таким образом вы можете вызывать окно сканирования и сохранять настройки сканирования в профили пользователя (обычные файлы).

Как правило производители систем сканирования поверх этого делают еще свой фирменное диалоговое окно, который редактирует следующие настройки: 1) Цвет; 2) Автоподача; 3) Разрешение; 4) Белая область; 5) Авто-корректировка текста; 6) Авто-доворот. Вы можете выставлять либо настройки сохраненные после вызова диалогового окна сканера, либо свои настройки, где вы все контролируете, либо выставлять сначала настройки сканера, а поверх свои. Обычно это три классических кейса. Некоторые функции поддерживаются сканерами, а некоторые нужно делать самому с использованием графических библиотек. Всегда нужен баланс денег и времени, не пытайтесь сделать из системы сканирования Фотошоп, лучше купите LeadTools. В реальности 300 DPI за глаза хватает, главное, чтобы это смог распарсать синьор в очках, а не ваш кривой OCR.

Протокол сканирования

Наш Веб-сервер был представлен на страницах этого журанала еще в январе в статье F# WebSocket Server. Здесь же мы просто покажем как будем ним пользоваться в качестве транспорта.

Для захвата изображений со сканера предлагется 3-арный CSV протокол со следующим заголовком:

Функция,Устройство,Профиль,Вызовы

Четвертое поле Вызовы содержит вызовы разделенные плюсом (+), а сами вызовы состоят из имени и (опционально) параметров, перед каждым из которых стоит минус (-). Например:

SCAN,DS-530,maxim,AUTOSCAN+AUTOFEED SCAN,DS-530,"",AUTOSCAN-1+COLOR-BW+DPI-300+PAGES-20

В случае когда третье поле Профиль пустое, никакой профиль, находящийся в агенте, не применяется перед сканированием. Когда четвертое поле пустое, тогда дополнительные настройки поверх профиля не применяются. Когда и третье и четвертое поля пусты, это означает, что никакие настройки CAPABILITIES не будут применятся перед сканированием:

SCAN,DS-530,,

Формат профиля сканирвания в сущности есть вызовы функций перед сканированием. Профиль можно передавать по сети при помощи следующего протокола:

Примеры:

SET,DS-530,maxim,AUTOSCAN-1+COLOR-BW+DPI-300+PAGES-20 GET,DS-530,maxim,""

При вызове функции GET приходит сообщение PROFILE:

PROFILE,DS-530,maxim,AUTOSCAN-1+COLOR-BW+DPI-300+PAGES-20

WebSocket Транспорт

В качестве транспорта используется протокол WebSocket и его имплементация, находящаяся в модуле ws.exe.

> ws = new WebSocket("ws://127.0.0.1:50220") > ws.onmessage = function (evt) { // тут вы получаете изображения console.log(evt.data); } > ws.send('SCAN,DS-530,,')

Протокол MQTT уведомлений

Модель работы уведомлений в промышленной среде следующее: модули предприятия оповещают клиентов и Windows Tray приложения при помощи CSV протокола по MQTT шине. При запуске, агент сканирования mia-agent соединяется с MQTT брокером, адрес которого задатается на конфигурационной форме FormSetup. Агент "МІА: Сканування" подписывается исключительно на уведомления ERP молуля "МІА: Документообіг". В настройках формы FormSetup таккже предлагается набор чекбоксов — типы объектов ERP модуля, уведомления про которые хочет получать пользователь в область уведомлений операционной системы. Например:

[+] Оброблено підлеглими
[+] На погодження
[  ] На підпис
[  ] Депутатські звернення

Полный перечень категорий должен быть реализован как Elixir модуль, часть системы "МІА: Документообіг", который посылает оповещения для "МІА: Сканування" через MQTT брокер.

Формат MQTT уведомлений

Уведомления посылаются в CSV формате:

Модуль, Тип, Приоритет, Идентификатор, Текст, URL

Примеры уведомлений:

СЕД, Документ, 1, ЗГ-001, Оброблено підлеглими, ?p=ВИХ_ЗГ-001_ДП СЕД, Документ, 2, 11214, На погодження, ?p=ВИХ_11214_ДП

Префиксы топиков:

/mia-crm/# -- Документооборот /mia-acc/# -- Персонал /mia-fin/# -- Учет и Отчетность



SCAN — Windows Tray Agent "МІА: Сканування", Companion Application of "МІА: Документообіг".