В современном мире услуги доставки становятся всё более популярными и востребованными, поэтому любая возможность автоматизации в этой сфере принесёт большую пользу как бизнесу, так и пользователям. В прошлых статьях нашего блога мы рассказывали о применении машинного зрения и нейронных сетей для распознавания ценников товаров в магазине, а также для распознавания комплектующих деталей. В этой статье мы расскажем о менее амбициозной (но не менее интересной) задаче – автоматизации оповещения клиентов о статусе их заказов с использованием чат-бота в Telegram, QR-кодов и реляционной СУБД SAP SQL Anywhere.  Нужно отметить, что такая задача разбиралась исключительно в образовательных целях, и решение пока ограничилось лишь игрушечным прототипом. Тем не менее, мы снова прибегли к машинному зрению, обработке QR-кодов, а также Telegram ботам. Мы прекрасно понимаем, что тема ботов в Telegram на Хабре практически полностью раскрыта, поэтому постараемся не заострять на этой части особого внимания. Отметим только, что выбор пал на Telegram из-за его популярности в России и удобного API. Также в конце статьи будет ссылка на Github репозиторий этого проекта, так что любой желающий сможет изучить его исходный код. Поэтому, если вам интересна тема уведомлений пользователей Telegram по QR-коду с live-трансляции веб-камеры и их потенциального применения, то милости просим под кат.   Как вы знаете, обычно уведомления о статусе онлайн-заказа приходят по e-mail, но сейчас интернет-магазины всё чаще информируют своих клиентов через мессенджеры. Когда мы начинали работать над этой задачей, то тоже решили, что через бот в мессенджере можно гораздо активнее информировать о статусе («принят», «прибыл на склад», «взят со склада», «в пути» и т.д.), не прибегая к рассылке спама по почте. Разумеется, важным условием является предоставление клиенту выбора «уровня» уведомлений – какие статусы своего заказа он хотел бы отслеживать, а также возможности и вовсе полностью от них отписаться. Как можно видеть на обобщённой блок-схеме ниже, мы подключили бота к веб-камере и сделали триггером отправки сообщения появление в эфире определённого QR-кода. Итак, пусть заказы наших клиентов хранятся в базе данных SQL Anywhere, и у нас есть оборудование, оснащённое веб-камерой (Raspberry Pi с PiCam, например) и подключённое к этой базе. Допустим, что заказанный клиентом товар доставлен на склад. На товаре наклеен QR-код, по которому однозначно определяется заказ в базе. Мы хотим, чтобы, как только QR-код товара появлялся в кадре складской веб-камеры, клиенту, заказавшему этот товар, отсылалось уведомление в Telegram о прибытии его заказа на склад. Причём в нём должны учитываться предпочитаемый язык текста и временная зона, в которой находится клиент, а также детали товара. Для нашего прототипа считается, что QR-код кодирует физический адрес клиента, но он вполне может кодировать что угодно, что позволяет идентифицировать заказ. Конечно же на практике такой QR-код должен кодировать нечто уникальное (UPC, например), но в нашей учебной модели каждый адрес соответствует единственному заказу. Вообще, подобный проект относительно легко переделать под другие задачи, связанные с распознаванием QR-кодов с эфира камеры и отсылкой сообщений в Telegram, в зависимости от содержания этих QR-кодов. Мы написали бота на Python, используя асинхронный фреймворк AIOgram для работы с Telegram Bot API и sqlanydb в качестве драйвера для SQL Anywhere. Для взаимодействия с веб-камерой, поиска и обработки QR-кодов мы воспользовались модулями OpenCV и NumPy. Таким образом, наш проект состоит из трёх основных частей: · работа с базой данных SQL Anywhere (через модуль sqlanydb); · параллельное подключение к стриму веб-камеры, поиск и обработка QR-кодов, которые в нём появляются (через модули OpenCV и NumPy); · работа самого Telegram бота для связи с клиентами (через фреймворк AIOgram). Далее мы более детально рассмотрим каждый из перечисленных пунктов и продемонстрируем работу нашего приложения. SAP предоставляет реляционную СУБД SQL Anywhere. Среди её плюсов – масштабируемость на десятки тысяч соединений, эффективный менеджмент потребляемых ресурсов и низкая нагрузка на память. Мы очень хотели её немного протестировать, потому выбор базы данных для приложения пал именно на неё. Сначала нам необходимо создать сам файл с базой. Это можно сделать через CLI-утилиту dbinit: Эта команда создаёт базу данных с администратором «admin» (и паролем «password123»), размером страницы в 4 килобайта и схемой, заданной в кодировке UTF-8. Теперь можем подключиться к созданной базе «orders.db» через SQL Central (программа устанавливается в комплекте с SQL Anywhere) и создать таблицу для хранения заказов. В нашем случае таблица создаётся следующим SQL-запросом: В итоге пример заказа в такой таблице может выглядеть примерно так:    Эта база данных затем легко подключается к нашему приложению через модуль sqlanydb: достаточно лишь добавить наши credentials (в нашем случае admin как UID и password123 как пароль) в переменные среды (в проекте мы их храним в .env файле и подцепляем через модуль dotenv). Теперь у бота появился полный доступ к таблице Orders: После успешного подключения к базе данных бот пытается найти и открыть камеру на устройстве, чтобы начать анализировать происходящее на эфире: Ниже приведена основная функция, отвечающая за рендер UI на окне с эфиром, поиск и обработку QR-кодов на эфире и передачу информации с найденного QR-кода дальше по цепочке. Немного поясним работу этой функции. Сперва мы проверяем, что устройство захвата изображения (она же веб-камера) открыто модулем OpenCV. Затем, так как мы следим, чтобы QR-код появился в определённой области кадра (а именно внутри квадрата с заданной стороной в центре), мы создаём массив из пар координат (x, y) четырёх вершин нашего квадрата со стороной `side` функцией create_square(). Далее мы функцией draw_bounds() рендерим на нашем кадре границы искомого квадрата, а также текст в зависимости от выбранного языка `lang`. То есть, draw_bounds() накладывает поверх стрима веб-камеры минимальный UI: Наконец, непосредственный анализ эфира и поиск объекта, похожего на QR-код происходит в следующей функции detect_inside_square():  Как вы успели заметить, по ходу написания кода мы старались его документировать с помощью докстрингов, поэтому ограничимся лишь описанием логики работы этой функции. Считается, что вокруг наших QR-кодов есть белый бордюр. Следовательно, мы начинаем с того, что отфильтровываем объекты в кадре по их «светлости» (яркости). Затем мы извлекаем контуры отфильтрованных объектов и вычисляем площади каждого из них. Если площадь объекта меньше минимального порога, заданного в переменной `area_min`, то нас такой объект не интересует, и мы переходим к следующему. Уточним, что все эти параметры можно передать скрипту через CLI-аргументы. Если же объект подходит по площади, то остаётся проверить, что он целиком содержится внутри квадрата, определённого выше. Для этого мы описываем вокруг объекта прямоугольник, сортируем его вершины так, чтобы первой вершиной в массиве была левая верхняя, а последней – левая нижняя, и проверяем довольно тривиальной функцией: Если все условия выполнены, то возможно, что наш объект является QR-кодом, и мы передаём изображение в квадрате (переменная `cropped`) в функцию detect_qr() для дальнейшего анализа. Если объект действительно оказывается QR-кодом, который можно расшифровать, то мы посылаем соответствующий запрос в нашу базу (напомним, что QR-код кодирует значения поля `address`), извлекаем информацию о заказе и отсылаем уведомление по Telegram ID клиента: Заметим, во-первых, что строчка отвечает за автоматическую конвертацию даты прибытия товара в часовой пояс клиента, а во-вторых, присутствует обработка исключений на те случаи, когда клиент, например, отключил бота или деактивировал свой аккаунт в Telegram. Стоит также упомянуть, что на данный момент в модуле sqlanydb, к сожалению, не предусмотрена sanitization SQL-запросов. Но модуль сейчас довольно активно разрабатывается, поэтому эта фича может появиться в ближайших релизах. А пока мы в функции `start()` воспользовались «костылём», который проверяет, что строка `address` содержится во множестве всех адресов из таблицы. Таким образом, если в полученном QR-коде (внезапно) закодировано нечто вроде «’; DROP TABLE Orders;», то наши данные останутся нетронутыми. После отправки уведомления наш бот будет ждать появления следующего QR-кода. Повторим, что это лишь демонстрация конкретного примера применения подобного приложения, и что можно относительно просто переписать код под решение смежных задач. Как мы обещали в начале, мы не станем особо вдаваться в детали. На Хабре более чем достаточно статей, посвящённых созданию ботов в Telegram (хотя, к нашему удивлению, статей, которые описывают работу именно с AIOgram на Хабре, мы не нашли). Отметим лишь, что мы использовали фреймворк AIOgram из-за асинхронности HTTP-запросов и качественной документации. Если читателям будет интересно, то мы как-нибудь подробно напишем о работе с этим фреймворком для создания Telegram ботов. На данный же момент мы ограничимся лишь демонстрацией хэндлеров, которые отвечают за установку предпочитаемого языка через ботовую команду /lang: Заметим, что возможности фреймворка AIOgram позволяют в перспективе настроить полноценную интернационализацию бота, но на текущем этапе (когда у нас всего три возможных сообщения и только два языка) вполне можно обойтись и заданием фраз вручную. Также обратим внимание, что для этих операций с нашей базой данных мы не делали никаких дополнительных проверок, так как единственное «незахардкоденное» значение для них – это ID пользователя в Telegram, а оно всегда является натуральным числом. В конечном итоге мы создали игрушечный прототип системы уведомления через популярный мессенджер с использованием легковесной СУБД от компании SAP. Разумеется, описанный пример очень далёк от внедрения в реальную логистику, но подобной цели перед нами и не стояло. Мы хотели в первую очередь исследовать возможности чат-ботов и связать их с машинным зрением. Поэтому и эту статью следует рассматривать в подобном ключе. Но если наша идея будет кому-то полезна для конкретной задачи, будем рады. Этот проект был представлен ещё летом 2019 года, задолго до текущей ситуации. И, как мы упомянули в самом начале статьи, в связи с нынешними реалиями службы доставки продолжают совершенствовать свою эффективность и повышать степень удобства для клиентов. Как показывают некоторые исследования и прогнозы, потенциал чат-ботов в этой области ещё совсем не исчерпан, и они продолжат бурно развиваться в течение ближайших нескольких лет. Поэтому мы тоже постараемся в перспективе улучшить текущий прототип и исследовать дополнительные случаи, в которых подобные приложения смогут себя проявить. Самое очевидное, что можно сейчас сделать – это разделить логику бота и попытаться интегрировать его с SAP Cloud Platform (SCP). Более того, можно воспользоваться средствами Conversational AI и подключить элементы машинного обучения. Исходный код нашего проекта открыт и доступен на Github. Мы постарались оставить подробные инструкции в README файле, на случай если вы захотите запустить нашего бота у себя. Если же у вас возникнут какие-либо замечания или предложения относительно самого кода, то, пожалуйста, напишите issue или откройте pull request. Мы будем рады вашему фидбэку! Автор - Артемий Геворков, стажёр отдела Co-Innovation Labs, SAP Labs CIS