Первый этап любого проекта — неправильно оценить сроки и бюджет проекта.
Статья изначально планировалась, как небольшой отчет о выполненной работе в целях немного поделиться опытом и себя показать. Но статья превратилась в достаточно эмоциональный и наглядный пример, что даже для простого проекта трудно оценить трудозатраты и сроки проекта.
Началось всё с простого ТЗ. Надо считать штрихкоды Честного знака с двух сканеров и записать данные с них в файлы. Вдобавок вывести на модуль ввода\вывода звуковую сигнализацию и сигнал для управления производственной линией, в и также распечатать транспортный штрихкод. Вот так это выглядит на рисунке:

Оба типа сканеров поддерживают много промышленных протоколов для обмена информацией, выбрать есть из чего. У меня есть наработки на С++\QT с поддержкой протоколов Modbus TCP\RTU, OPC UA, которые уже почти доросли до программной платформы уровня SCADA системы. Я сразу определился, что со сканером верификации, который проверяет считываемость кода, буду работать по OPC UA, а со сканером агрегации, который связывает несколько штрихкодов честного знака в один транспортный, буду работать по MODBUS TCP. По срокам получилось пара недель. Пара, тройка дней на интерфейс и прочую инфраструктуру программы, пара дней на процесс верификации и три, четыре дня на процесс агрегации. Еще неделя на непредвиденные сложности и пробную эксплуатацию. Итого 3 недели. Поехали!
Верификация
Верификация — процесс чтения распечатанных штрихкодов с продукта и запись их в файл, для дальнейшей обработки. Для начала я занялся сканером верификации Datalogic MATRIX 220. Первым делом я попытался обнаружить его по протоколу OPC UA. Быстро, конечно, не получилось — пришлось смотреть настройки сканера. Конечно же сервер OPC UA в настройках сканера был отключен. Чтобы его включить, как оказалось, надо всего лишь поставить галочку в настройках. Но при этом надо выполнить ряд условий:
Надо отключить процесс сканирования. Чем мешает процесс сканирования изменению коммуникационных настроек, не совсем понятно, но будем считать, что всё-таки это логично – любые настройки изменяются при отключенном процессе сканирования. Изменение настроек «на лету» не про этот сканер.
Нужно выбрать пользователя. Также будем считать, что это логично, но это из разряда «надо знать». Сама программа не подскажет, что настройки недоступны из-за недостатка прав доступа.
После того как сервер OPC UA включен в настройках, сканер надо перезагрузить! Да, вот так! Выключить питание и включить питание. Если кто то подумал, что программа настройки большим окошком напомнила перезагрузить сканер, то он ошибся. Это тоже из разряда «надо знать».
Итого примерно час для того, чтобы включить сервер OPC UA, если не знаешь того, что «надо знать». В дальнейшем нареканий к реализации протокола OPC UA в сканере у меня не возникло. Протокол отлично работает как периодическим опросом, так и по подписке.
Запустив программу рабочем ноутбуке, убедился, что коды читаются и записываются в файл, можно приступить к следующему этапу. Не тут-то было! Заказчик задал абсолютно справедливый вопрос! А как мне убедиться, что те штрихкоды, которые отсутствуют в файле именно сканер не считал, а не твоя программа пропустила? Я порылся в данных сканера по OPC UA и не нашел переменной в которой бы хранился неправильно считанный код или флага, что код не считан, или может какой то код ошибки. Сюрприз! Озаботившись риторическим вопросом «Ну как так то?!», я стал искать везде по переменным OPC UA сервера, и нашел статистику считанных и несчитанных кодов. Чтобы сравнить число кодов в программе и прошедших через сканер этого вполне достаточно. И в целом логично, но осадочек остался. Ну почему нельзя рядом с успешно считанным штрихкодом создать переменную\тэг с информацией об ошибке?! Немного посетовав, по древней программистской традиции, на нехороших разработчиков ПО этого сканера, я сказал себе, что уж у другого сканера программное обеспечение точно будет поудобнее. Наивный!
С учетом визитов на объект прошло 3 дня.
Создание дистрибутива
Настало время установить программу на компьютер на объекте, чтобы заказчик мог потихоньку внедрять её в работу и выявлять ошибки. В дистрибутиве QT есть прекрасная утилита WinDeployQT. С её помощью я быстро создал дистрибутив и перед поездкой на объект проверил на соседнем компьютере. Программа ожидаемо не запустилась и выдала несколько ошибок об отсутствующих dll. Я скопировал недостающие dll и попробовал снова. Программа выдала новую порцию недостающих dll. Не беда, и скопировал снова недостающие dll… Так повторилось раз 5-7. Надежда на то, что уж на этот раз программа точно заработает с каждым разом только крепла. И вот наконец программа не выдала ошибок и благополучно… аварийно завершила свою работу. Настало время применить ну не то чтобы тяжёлую артиллерию, но вручную проверить зависимости каждой dll. Для этого я воспользовался просмотрщиком(Lister) от Total commander. Ну просто был под рукой, функционала достаточно. Проверив каждый файл, скопировав несколько библиотек я ничего не добился – программа по-прежнему не запускалась. Тяжелая артиллерия не помогла, надо было что-то придумать, но ничего не приходило в голову. Тогда я применил оружие массового поражения. Я просто скопировал в папку к исполняемому файлу программы все библиотеки из папки QT\bin. Не помогло. Далее я скопировал все библиотеки из папок bin и lib компилятора. Опять не помогло. Остатки самообладания улетучились. Я начал смотреть какие ещё библиотеки можно скопировать. У библиотеки QT есть ещё инструментарий плагинов. Я скопировал все плагины. Ну конечно же это не помогло! Еще несколько часов хаотичного копирования и удаления библиотек принесли заслуженный результат. Оказалось, когда я проверял зависимости каждой библиотеки, я забыл проверить зависимости плагинов. Вот долгожданный ответ: Программа использует библиотеку моей разработки plcaccess.dll, которая использует Qt6OpcUa.dll, которая использует плагин open62541_backend.dll, который использует… барабанная дробь… библиотеки из Strawberry Perl: libcrypto-1_1-x64__.dll и libstdc++-6.dll. Вернее, это файлы OpenSSL и С компилятора GNU, но находятся они в папке Bin Strawberry Perl. Получается, что QT тащит с собой Perl и Python, чтобы сохранить зависимости 3dparty библиотек. И в целом то всё логично, но логично это становиться уже постфактум.
Перенести программу на компьютер на объекте заняло 2 дня, с учетом дороги.
Агрегация
Сканеры агрегации необходимы для того, чтобы связать воедино штрихкоды Честного знака, индивидуальные для каждой единицы товара, и штрихкод SSCC – серийный код транспортной упаковки. В конкретном случае используются сканеры HikRobot 5060. Планировал я общаться со сканерами по протоколу MODBUS TCP. Но китайцы остались верны себе и концепции MVP. По документации сканер сканирует до 50 штрихк��дов с одной картинки. И да сканирует - проверено! По документации сканер выкладывает отсканированные коды в область Holding Registers Modbus TCP. И да, выкладывает! Только область Holding Registers около одного килобайта и в эту область помещается чуть больше десятка штрихкодов Честного знака. Хотите больше – пожалуйста! Но не по Modbus TCP! Ладно, буду использовать родной протокол HikRobot. Но внятного описания протокола на просторах интернета я не нашел. Я даже написал в поддержку с просьбой поделиться протоколом, но в ответ пришло письмо с просьбой написать какой сканер, где куплен, серийный номер, версию прошивки и прочую не имеющую отношения к делу информацию. Понятно… В ответ на всю эту информацию пришлют ссылку на раздел download официального сайта. Пока ждал ответа от поддержки попробовал настроить передачу данных по FTP и, как ни странно, всё очень быстро получилось. FTP сервер идет отдельной утилитой в комплекте с программой настройки сканера и заточен на общение со сканером. Сканер записывает файл с результатами сканирования в папку с IP адресом сканера, годом, месяцем и днем. Только небольшой нюанс – у сканера нет часов! Ну это логично – зачем сканеру часы?! Так что папка всегда “1970/1/1”. Меня это более чем устроило и опустившись до уровня прошлого века стал считывать информацию через файл.
С формированием кода SSCC никаких проблем не возникло, разве что пришлось подтягивать инструментарий для работы с таблицами БД, потому что настройки для формирования кода SSCC, как оказалось, нужны в разрезе нескольких юридических лиц. И не то, чтобы это было сложно, но работы прибавилось.
С учетом нескольких визитов на объект разработка агрегации заняла 5 дней.
Принтер штрихкодов
Штрихкод транспортной упаковки использует формат GS1-128. Раньше он назывался EAN128 и UCC128. Распечатать его на принтере этикеток простейшая задача, нужно просто взять соответствующий шрифт. В интернете много и библиотек для печати кодов и шрифтов для их отображения. Решил обойтись просто шрифтом и скачал первый попавшийся шрифт для EAN128. Для проверки штрихкода выводил на его экран и сканировал утилитой с телефона. И конечно же он не сканировался. Попробовав несколько вариантов, несколько шрифтов я за полдня так и не смог правильно отобразить штрихкод. Возможно спецсимвол FNC1 неправильно представлялся этими шрифтами. Возможно есть некоторые отличия между форматами GS1-128 и форматами тех шрифтов(EAN128), которые я использовал. Надеюсь, знающие люди в комментариях подскажут в чем дело. Мне с самого не нравилось в идее со шрифтами, что шрифты нужно устанавливать в операционную систему. Поэтому, то, что шрифты не заработали меня даже чуточку обрадовало – есть повод сделать лучше без использования шрифтов, хоть это и займет больше времени. Посмотрев несколько библиотек, остановился на Promixis code128. Библиотека без использования шрифтов просто, элегантно и быстро рисует на канве линии необходимой толщины. В итоге реализация печати штрихкодов заняла около полутора рабочих дней, хотя я рассчитывал на один рабочий день.
Модуль ввода\вывода
В качестве модуля ввода\вывода в проекте внезапно нарисовался полноценный контроллер! Главными факторами выбора были наличие и цена. И как оказалось контроллер Delta DVP SS2 был в наличии и стоил сопоставимо с модулями ввода\вывода от отечественной компании OWEN. Очевидно, что, в отличие от модуля ввода\вывода, для контроллера нужно писать программу. Хоть и простая программа, но на неё тоже нужно время, к тому же необходимо было установить на рабочий компьютер среду программирования. Здесь единственный раз за весь проект меня ждал приятный сюрприз. Оказывается, программировать Delta DVP SS2 можно не только через кабель программирования и специальное гнездо, но и через второй порт COM2 RS485. Это позволило достаточно быстро перепрограммировать контроллер, который был смонтирован в труднодоступном месте.
Во время программирования работы с контроллером, оказалось, что в моей программной платформе не реализована работа в отдельном потоке и задание параметров связи для протокола RS485\MODBUS RTU. Пришлось быстро дописывать и отлаживать уже на объекте.
Таким образом задача выдать несколько сигналов на модуль ввода\вывода растянулась на 3 дня.
Доработки и исправление ошибок
Неожиданным и абсолютно непонятным поведением программы стало критическое замедление работы программы к концу рабочего дня. Да, вот прям настолько критическое, что запись в файл иногда завершалась с ошибкой. Причем это замедление влияло на все потоки программы. В абсолютных величинах это замедление составляло 3-4 секунды. Оптимизация работы с файлами в программе ожидаемо ничего не дала – запись пары килобайт на SSD не может настолько тормозить. Получив удаленный доступ к компьютеру на объекте в диспетчере задач, я увидел, что процессорное время забирают системные процессы защитника Windows. Также на компьютере стоял Kaspersky с истекшей лицензией. Kaspersky был успешно удален, а в защитник Windows были добавлены в исключения, те папки, в которых происходила работа с файлами. Проблема ушла, но в чем конкретно была причина – непонятно. Может в Касперском, может в защитнике Windows, возможно в их взаимодействии.
Сколько рабочего времени было потрачено на исправление ошибок и доработки точно не сказать, потому что до нового года «горят планы» и времени на эксперименты нет, а после нового года нет заказов, линия остановлена.
Заключение
За 3 недели, до начала нового проекта, я всё-таки успел закончить полнофункциональную версию программы. Конечно же, в дальнейшем вносилось много дополнений и исправлений. Этап, назовем его пробной эксплуатацией, растянулся по календарю на срок более 2 месяцев.
Статья задумывалась изначально как нечто чисто программистское, но вдруг приобрела некоторые философско-экономические черты. Из шутки «Первый этап любого проекта – неправильно оценить сроки и бюджет проекта» логично вытекает следствие – «Получит проект тот разработчик, который наиболее неправильно оценит сроки и бюджет проекта». А для заказчика из всего этого следует, что «проект никогда не будет сдан в срок».
В любом проекте есть свои трудности и подводные камни. Это могут быть и объективные трудности с китайским железом и китайским ПО, и субъективные трудности, ибо все мы люди, а людям свойственно ошибаться. С точки зрения исполнителя заложить 50% запаса по времени это способ не обанкротиться самому и не подвести заказчика со сроками, а с точки зрения заказчика это обман и надувательство необоснованное увеличение сроков и стоимости.
Так и продолжается вечная борьба заказчика с исполнителем, экономии со здравым смыслом. Пока тотальная экономия на всём, к сожалению, побеждает. Остается только пожелать заказчикам и руководителям побольше здравого смысла, а исполнителям программистам - терпения и удачи!
P.S. Просто ещё один яркий пример, который не удалось вплести в сюжет статьи. Банальнейшая, простейшая задача: необходимо поехать на объект и залить новую версию программы на контроллер. Программа готова и протестирована. Делов на 2 часа. Намечается плановый останов объекта – целых 2 дня. Загрузить, проверить и уехать. Какие могут быть проблемы? И в те 2 дня, когда был запланирован останов объекта и обновление ПО, велись ремонтные работы на высоковольтной подстанции, питающей объект. Логично же! А потом оказалось, что контроллер krevis не может общаться с устройствами из другой подсети. Многоходовый квест с получением и настройкой маршрутизатора на проброс портов занял 3 дня. C учетом других мелких технических и организационных проблем, работы удалось завершить за 8 дней. С моей стороны я молодец – закрыл все замечания, сдал эксплуатации, чтобы не ездить второй раз. А что подумал заказчик, когда запланированные 2 дня растянулись на 8, история умалчивает...
