Проектирование простой системы учёта товаров на небольшом складе
Задача соответствующего учёта складских остатков является достаточно актуальной и рассмотрена во множестве работ. Для этой цели использовано большое количество различных подходов. Однако тот подход, который мы собираемся рассмотреть в этой статье, является достаточно интересным, так как для этого используется разработка под нашу любимую Arduino IDE.
Современная экономика базируется на большом количестве товаров, перемещающихся из одного места в другое. Любая современная фирма, которая имеет дело с товарными запасами — обязана соответствующим образом поставить систему их учёта.
Это необходимо не только для соответствующего пополнения товарных запасов в нужные сроки (чтобы не было разрывов между расходом товаров со склада и их поступлением от поставщиков), но и для аналитических целей, когда по темпу расхода товаров со склада можно предугадать потребность в таких запасах на будущие периоды: день, неделю, месяц, год.
Несмотря на то что указанное уже звучит страшно для большинства читающих и «светит» использованием серьёзных корпоративных продуктов для учёта, такой учёт можно вести весьма простым способом! Причём в данном случае под «простым» не подразумевается «плохим», так как, несмотря на минимальные расходы, позволяет весьма эффективно учитывать запасы в рамках компании.
Предположим следующую распространённую ситуацию: некая компания поставляет товар в торговые точки, разбросанные по городу.
С некоторой периодичностью товар в этих точках расходуется, и необходимо постоянно пополнять запасы, не допуская их полного исчерпания. В большинстве компаний этим занимаются специальные сотрудники, которых называют «торговыми представителями». Эти сотрудники посещают торговые точки, ведут учёт товаров в конкретных точках, проводят мерчандайзинг (расстановку товаров в торговом зале), а также производят заказ требующихся товаров (это осуществляется как при прибытии обратно в офис, так и непосредственно в торговой точке, перед отправкой заказа по электронной почте).
Казалось бы, отлаженная система, действующая во множестве компаний, какие тут могут быть проблемы? Однако проблема приходит с неожиданной стороны: текучка кадров. Дело в том, что позиция торгового представителя является не сильно высокооплачиваемой, что приводит к соответствующей текучке кадров. Ввиду этого постоянно возникают проблемы, когда компанию некому проинформировать о наличии/отсутствии/количестве товарных остатков в конкретной товарной точке. Это приводит к убыткам головной организации, потому что, как правило, эта организация платит кроме процента с каждой проданной единицы товара ещё и определённые арендные платежи за размещение товара в определённом месте торгового зала (к примеру, третья полка снизу от пола стоит гораздо дороже, чем 1 полка снизу, около самого пола. Это связано с тем, что третья полка снизу находится, с точки зрения маркетологов, на самом оптимальном месте и обеспечивает самые высокие продажи).
Теперь вернёмся к нашему вопросу об автоматизации склада. Казалось бы, что здесь можно автоматизировать, если товар расположен на полке? Действительно, в данном случае трудно что-либо предпринять, однако эту проблему можно устранить следующим образом: использование вендингового аппарата собственного производства.
Предположим, что такой аппарат может выдавать одну единицу товара, в процессе выдачи которого происходит её автоматический учёт определённым датчиком (мы сейчас не будем предметно останавливаться на конкретной конструкции такого аппарата, так как он может быть выполнен в абсолютно разных исполнениях, в зависимости от типа товара).
Кроме того, на базе рассмотренного ниже принципа можно создать автоматизированные склады «ячеистого» типа, которые будут вести учёт расходоа и поступлений товаров в автоматическом режиме, а также будут информировать центральный офис о состоянии товарных остатков в конкретном подразделении.
Использование вендингового аппарата или ячеистого автоматизированного склада даёт нам широчайшие возможности по контролю товарных остатков в нём. Но сразу встаёт вопрос: мало учитывать товар, необходимо также и в соответствующем темпе информировать о происходящем в каждой конкретной точке головной офис компании.
Для этого необходимо организовать соответствующую передачу данных через протоколы беспроводной связи. Мы не будем рассматривать более простой вариант, когда микроконтроллер подключался бы по wi-fi к точке доступа, находящейся в зоне доступности, так как это практически «сказочный» вариант. Потому что обычно всё гораздо сложнее: точки доступа нет (или к ней нельзя подключаться) и необходимо организовать передачу данных по интернету через мобильную сеть — самостоятельно.
В свою бытность, в начале 2010-х годов, автору статьи приходилось решать подобную задачу, и тогда для её решения использовалась связка из Arduino Ethernet и GPRS шилд для Arduino.
Эта связка вполне хорошо работала, однако в настоящее время, на взгляд автора, такая связка является устаревшим решением. Поэтому лучше использовать более современные решения на базе esp32, так как в этом случае мы получаем все плюшки этой платы: мощный процессор, wi-fi, Bluetooth, а в этом конкретном случае ещё и интернет через мобильную сеть, именно поэтому мы будем использовать плату TTGO T-Call ESP32 SIM800L.
Источник картинки: randomnerdtutorials.com
В общем случае схема работы нашей системы будет выглядеть следующим образом:
То есть нам необходимо производить учёт в конкретной точке, далее отправлять эту информацию на центральный сервер, где она будет записываться в базу данных MySQL, откуда она может быть считана любыми средствами визуализации и ведения отчётности.
В простом случае можно использовать скрипт, который будет выводить данные в графическом виде.
Следует отметить, что это решение является несколько ограниченным, ввиду того что мы передаём данные, обращаясь к скрипту на сервере, который уже настроен и предназначен только для внесения изменений в базу данных. Однако существует гораздо более гибкое решение, которое вы можете попробовать проработать сами: библиотека, позволяющая вносить изменения в базу данных напрямую, без скрипта-посредника на сервере.
Справедливости ради следует отметить, что для большинства применений такая гибкость будет излишней, но можно держать эту возможность в уме.
В качестве основы мы возьмём 3 подхода. Первый не совсем соответствует потребностям, так как построен на основе wifi-сети. Второй — позволяет пересылать данные через мобильную сеть, но энергозатратен. Третий — энергоэффективен, но совершенно не работает с сетью :-)
Сделаем на их базе наш гибридный, четвёртый вариант.
Продолжая прорабатывать нашу систему, сделаем следующие допущения: предположим, что у нас к микроконтроллеру подключены 2 устройства:
- Датчик, отвечающий непосредственно за выдачу товара (в качестве такового может быть использован любой датчик инфракрасного типа, датчик Холла, мимо которого проходит товар во время выдачи; либо датчик реагирует на открытие специальной дверцы выдачи товара т.д., реализации могут быть разные.
- Кнопка, которая обновляет количество запасов в конкретном автомате и устанавливает их максимальное количество, возможное для размещения в ёмкости для товаров. В этом конкретном случае мы взяли количество равное 40. Но это не является аксиомой, это просто взято для примера.
Алгоритм работы выглядит следующим образом: открывается специальная дверца для помещения товаров вовнутрь, товары укладываются, после чего нажимается кнопка, которая устанавливает максимальное количество товаров, и эти данные отправляются в базу.
Кроме того, для обеспечения энергоэффективности работы автомата мы будем использовать режим глубокого сна микроконтроллера, когда он практически всё время спит и пробуждается только при возникновении соответствующих событий на пинах.
Рассмотрим более предметно отдельные участки кода.
Для лучшей читабельности кода и его визуального упрощения отдельные участки разнесены на вкладки:
Сначала мы инициализируем настройки GPRS:
// Ваши данные GPRS (оставьте пустыми, если не требуются)
const char apn[] = ""; // APN (для примера: internet.vodafone.pt)
const char gprsUser[] = ""; // GPRS User
const char gprsPass[] = ""; // GPRS Password
// SIM card PIN (оставьте пустым, если не требуются)
const char simPIN[] = "";
Далее нам необходимо ввести настройки сервера с базой данных, к которому будет производиться подключение (тут же мы обозначим веб-скрипт, который будет принимать наши POST-обращения):
const char server[] = "example.ru";
const char resource[] = "/post-data.php"; // адрес скрипта
//для помещения в базу данных
const int port = 80;
Следует отметить, что при подключении к скрипту мы используем ключ API, который должен совпадать в нашем коде, здесь в Arduino IDE, с таким же ключом, который должен быть прописан в самом веб-скрипте.
Далее вводим настройки конкретной ячейки:
int GoodsCounter = 40; //сколько товара помещается в ячейку
Интересным моментом является добавление в программу пинов, которые вызывают пробуждение микроконтроллера ото сна. Для этого используются соответствующие битовые маски этих пинов (подробная инструкция, как добавлять новые пины, вызывающие пробуждение системы, имеется здесь):
//пины, которые вызывают пробуждение
#define BUTTON_PIN_BITMASK 0x8004 // GPIOs 2 and 15
Полностью функция, которая будит микроконтроллер, выглядит следующим образом:
esp_sleep_enable_ext1_wakeup(BUTTON_PIN_BITMASK,ESP_EXT1_WAKEUP_ANY_HIGH);
После полной инициализации и загрузки микроконтроллер уходит в сон по вызову соответствующей функции:
esp_deep_sleep_start();
Ещё одним интересным моментом является то, что метод loop () в данном случае не используется вообще.
Функция void toDataBaseLoader (), выполненная в отдельной вкладке, как можно догадаться по названию, служит для загрузки данных о товарных остатках в централизованную базу на сервере. Она содержит ряд служебных элементов, информирующих юзера о происходящем в данный момент методом вывода сообщений в Serial, а также соответствующим образом сконфигурированный метод POST, который представляет собой сформированную строку, отправляемую на сервер:
// Готовим данные HTTP метода POST
String httpRequestData = "api_key=" + apiKeyValue +
"&количество товара=" + String(GoodsCounter) + "";
Также у нас имеется 2 функции, одна из которых просто выводит сообщение о том, какая причина вывела микроконтроллер из режима сна — void print_wakeup_reason ():
//функция печатает причину вывода из сна
void print_wakeup_reason(){
esp_sleep_wakeup_cause_t wakeup_reason;
wakeup_reason = esp_sleep_get_wakeup_cause();
switch(wakeup_reason)
{
case ESP_SLEEP_WAKEUP_EXT0 :
Serial.println("Wakeup caused by external signal using RTC_IO"); break;
case ESP_SLEEP_WAKEUP_EXT1 :
Serial.println ("Wakeup caused by external signal using RTC_CNTL"); break;
case ESP_SLEEP_WAKEUP_TIMER :
Serial.println("Wakeup caused by timer"); break;
case ESP_SLEEP_WAKEUP_TOUCHPAD :
Serial.println("Wakeup caused by touchpad"); break;
case ESP_SLEEP_WAKEUP_ULP :
Serial.println("Wakeup caused by ULP program"); break;
default :
Serial.printf("Wakeup was not caused by deep sleep: %d\n",
wakeup_reason); break;
}
}
А также функция, которая выводит номер конкретного пина, который явился причиной пробуждения — int wakeup_reason_PIN ():
//функция, определяющая, какой пин вызвал пробуждение
int wakeup_reason_PIN ()
{
uint64_t GPIO_reason = esp_sleep_get_ext1_wakeup_status();
GPIO_number = (log(GPIO_reason))/log(2);
Serial.print("GPIO вызвавший пробуждение: GPIO ");
Serial.println((log(GPIO_reason))/log(2), 0);
if (GPIO_number == 2) //если сработал датчик выдачи товара
{
if (GoodsCounter>0)
{
GoodsCounter--;
}
}
//если сработал датчик загрузки товара поставщиком
else if (GPIO_number == 15)
{
GoodsCounter == 40;
}
//загружаем в базу текущее состояние товарных запасов
ToDataBaseLoader ();
}
Скачать готовый скетч можно тут.
В завершение следует сказать, что рассмотренный подход очерчивает лишь общие рамки реально действующей системы. Код представленной выше наверняка, требует соответствующей доработки, ввиду того что у автора непосредственно на руках не было платы TTGO T-Call ESP32 SIM800L, чтобы произвести полную отладку.
Кроме того, реально действующая система должна включать ещё и исполнительный механизм (например, двигатель, который будет выталкивать товар из определённой ячейки), а также систему приёма платежей.
Кроме того, рассмотренный подход является упрощённым, так как в реальной ситуации наверняка потребуется выдавать за один раз не одну единицу товара, а некоторое количество сразу. В случае вендинговых аппаратов это достаточно просто. В случае же, если указанный подход будет использован для автоматизации небольшого склада, могут потребоваться дополнительные усложнения. Например, если кладовщик берёт товар из определённой ячейки в количестве более одной штуки, то можно рядом с каждой ячейкой расположить кнопку и микро LED мониторчик, при нажатии на кнопку на мониторчике будет выводиться соответствующее количество товара, которое будет взято сейчас из ячейки. После закрытия дверцы ячейки система будет считать, что забор товара завершён, и отправит изменившееся количество товара в базу данных.
Таким образом, система, которую мы рассмотрели в этой статье, может служить не только для создания вендинговых аппаратов с оперативным уведомлением офиса о состоянии товарных запасов, но и для создания полноценных складов ячеистого типа, которые в автоматическом режиме учитывают приход и расход товара и ведут централизованную базу.
НЛО прилетело и оставило здесь промокоды для читателей нашего блога:
— 15% на все тарифы VDS (кроме тарифа Прогрев) — HABRFIRSTVDS.
— 20% на выделенные серверы AMD Ryzen и Intel Core — HABRFIRSTDEDIC.
Доступно до 31 декабря 2021 г.