Функция печати этикеток Data Matrix в C++ и LabView
Обо мне
Я инженер-испытатель на производстве и программист-самоучка на Visual C++.
В качестве инженера-испытателя на производстве моя основная обязанность заключается в разработке специализированного программного и аппаратного обеспечения, которое функционально тестирует продукты моей компании, чтобы гарантировать, что продукты работают в соответствии с ее спецификациями.
Я в основном отвечаю за разработку программного обеспечения и с 2001 года разрабатываю пользовательские приложения для Windows на Visual C++.
Недавно я также начал разрабатывать приложения в LabVIEW. В конце концов руководство распорядилось, чтобы с этого момента это была единственная тестовая программная платформа для стандартизации вместе с нашими европейскими коллегами, которые годами используют LabVIEW.
Проблема, которую я хотел решить
Маркетинг потребовал, чтобы перед отправкой покупателю на линейке продуктов была размещена этикетка Data Matrix на каждом продукте определенного типа модели. На этикетке должен быть как штрих-код Data Matrix, так и читаемый человеком текст.
Информация на этикетке будет уникальной для каждого продукта и будет содержать следующее:
- Тип модели
- Версия продукта
- Код даты
- MAC-адрес
- Серийный номер
Оптимальная область для печати этикетки Data Matrix находилась в той же рабочей зоне, где производилась эта линейка продуктов. Однако площадь помещения была в дефиците, и мы не могли расширить рабочую зону, чтобы добавить еще одну станцию печати, которая используется для создания основных этикеток для этой рабочей зоны.
В рабочей зоне было два ПК с двумя разными функциональными тестерами: устаревший тестер, разработанный в 2005 году с приложением LabVIEW, разработанным зарубежным коллегой, и более новый тестер, разработанный в 2015 году с разработанным мной приложением Visual C++.
Поскольку я разработал последнюю версию функционального тестера для этой линейки продуктов, теперь я отвечаю за оба функциональных тестера. Итак, руководитель производственного проекта обратился ко мне за помощью в решении этой проблемы.
Руководитель производственного проекта предположил, что, по его мнению, было бы лучше, если бы мы установили стороннее программное обеспечение на ПК обоих функциональных тестеров. Идея заключалась в том, чтобы стороннее программное обеспечение форматировало и печатало этикетку Data Matrix в конце теста.
Однако при проверке мы обнаружили некоторые сложности, которые помешали бы реализации его предложения:
Во-первых, если оба ПК будут использовать стороннее программное обеспечение, они должны быть подключены к внутренней сети компании, чтобы иметь возможность подключаться к серверу лицензий. Оба этих ПК были автономными ПК. Один работал под управлением Windows XP, а другой — под стандартной Windows 7. Оба компьютера были несовместимы с внутренней сетью нашей компании. Мы не хотели покупать пару лицензий для этих ПК и не хотели обновлять ПК, поскольку нам пришлось бы отключать тестеры. Это отрицательно сказалось бы на производственных возможностях рабочей зоны в период пиковой производительности.
Далее, поскольку каждая этикетка Data Matrix должна была быть уникальной для каждого продукта, самым простым способом получения уникальной информации о продукте было использование тестовых программных приложений. Однако ни для одного из приложений, LabVIEW или Visual C++ не существовало надежного способа взаимодействия со сторонним программным обеспечением — даже через командную строку.
Наконец, эта функция должна была быть реализована в течение трех недель.
Поэтому я предложил руководителю производственного проекта добавить в тестовое программное обеспечение функцию форматирования и печати этикетки Data Matrix в конце теста без необходимости использования стороннего программного обеспечения. Я бы реализовал эту функцию на обоих функциональных тестерах.
Почему было лучше реализовать функцию печати этикеток Data Matrix в C++ и LabView?
Во-первых, у меня есть опыт использования языка программирования Zebra II (ZPL II) в предыдущих проектах — создание и форматирование штрих-кодов Code 128.
Итак, я просмотрел PDF-файл руководства ZPL II и увидел, что ZPL II может создавать этикетки Data Matrix, а также этикетки со штрих-кодом Code 128.
Я был еще больше взволнован, потому что знал, что уникальная информация о продукте, необходимая для создания этикетки Data Matrix, содержится на основной этикетке каждого продукта в виде штрих-кода Code 128.
Фактически, данные штрих-кода продукта захватываются тестовым приложением как средство для фактического запуска тестовых операций. Кроме того, в конце теста захваченные данные штрих-кода сохраняются в EEPROM продукта, чтобы действовать как MAC-адрес продукта. Таким образом, было бы просто проанализировать данные штрих-кода продукта и переформатировать эту информацию для создания и печати этикетки Data Matrix.
Стек технологий
Для кода C++ и LabVIEW идея заключалась в создании двух основных компонентов — строки Data Matrix и строки ZPL II.
Строка Data Matrix — это в основном отформатированная строка для создания штрих-кода Data Matrix, и она будет встроена в строку ZPL II.
Строка ZPL II будет содержать все символы ZPL II для форматирования штрих-кода и правильного размещения штрих-кода Data Matrix и удобочитаемого текста.
Теперь для кода C++ я использовал классы Microsoft Foundation, CString а также CTimeчтобы создать строку матрицы данных, и я использовал класс стандартной библиотеки шаблонов острстрим для создания строки ZPL
Для кода LabVIEW я создал VI (или Виртуальный инструмент — версия LabVIEW функции/процедуры) с именем Print_DM_Address.vi
Процесс создания функции печати этикетки Datamatrix Label в C++ и LabView
Сначала я начал с кода C++, так как я лучше знаком с C++ (и у меня есть очень сильный уклон в пользу C++).
Я создал структуру с именем DataMatrixCoder. Цель этой структуры в основном состоит в том, чтобы проанализировать информацию о продукте из штрих-кода Code 128 на этикетке продукта и создать строку информации о продукте DMC.
#include <afx.h>
// Forward Declaration to connect to the test software's main window.
class CMainWin;
const CString SSNCODE = "1P";
const CString ASNCODE = "31P";
const CString REVCODE = "2PFS";
const CString PDATECODE = "16D";
const CString MACCODE = "23S";
const CString SNCODE = "43S";
struct DataMatrixCoder
{
public:
DataMatrixCoder(CMainWin* win);
~DataMatrixCoder();
void ClearData();
// Create the string that creates the Datamatrix barcode
void CreateDMCodeString(CString& dmcString);
CString ssnStr; // SSN
CString asnStr; // ASN
CString prodRev; // Product revision
CString prodDate; // Production Date
CString macAddress; // MAC Address
CString serialNum; // Last 5 digits of top-level barcode
private:
// Create the date code from CTime
void CreateProductionDate();
// Store data into prodRev, macAddress and serialNum from product label's
// barcode.
void ParseBarcode();
CMainWin* mainWin; // Pointer to main window
};
я сделал DataMatrixCoder структуру вместо класса, чтобы я мог создать более читаемый код, в частности, при доступе DataMatrixCoderэлементы CString для форматирования удобочитаемого текста метки Data Matrix в строку ZPL.
Реализация этой структуры относительно проста, поэтому я остановлюсь только на основных методах:
DataMatrixCoder::DataMatrixCoder(CMainWin* win): mainWin(win)
{
// Product Data is a struct that contains the product information such as the
// product's model number (prodData->prodNum)
ProductData* prodData = mainWin->GetProductData();
asnStr = prodData->prodNum;
// "BPZ" is a marketing code that was requested for this field.
ssnStr = "BPZ:" + asnStr;
ParseBarcode();
CreateProductionDate();
}
void DataMatrixCoder::CreateDMCodeString(CString& dmcString)
{
dmcString.Format("%s%s+%s%s+%s%s+%s%s+%s%s+%s%s",
SSNCODE, ssnStr,
ASNCODE, asnStr,
REVCODE, prodRev,
PDATECODE, prodDate,
MACCODE, macAddress,
SNCODE, serialNum);
}
void DataMatrixCoder::CreateProductionDate()
{
CTime currentTime = CTime::GetCurrentTime();
prodDate = currentTime.Format("%y%m%d");
}
void DataMatrixCoder::ParseBarcode()
{
// TestData is a struct that is used to hold important test data such as
// the product's Code 128 barcode information.
TestData* data = mainWin->GetTestData();
CString barcode = data->barcode; // the product's Code 128 barcode.
// Parse barcode for the product revision.
// The product's Code 128 barcode could contain either an 'A' or 'K' at
// index 8.
// This would have to checked to correctly retrieve the two-character
// product revision code.
short revIndex = 0;
if (barcode[8] == 'A')
{
revIndex = 10;
}
else if (barcode[8] == 'K')
{
revIndex = 11;
}
prodRev = barcode.Mid(revIndex, 2);
// Create MAC address from barcode.
// For this product, the entire barcode is written into the product's
// EEPROM to act as its MAC address (hyphens and spaces are removed)
macAddress = barcode;
macAddress.Remove('-'); macAddress.Remove(' ');
// Parse barcode for serial number (last 5 barcode characters)
serialNum = barcode.Right(5);
}
Ранее я создал класс под названием серийный принтер и отдельный заголовочный файл с именем Константы ZPL.h для создания строк ZPL и печати штрих-кодов Code 128.
// ZPL CONSTANTS.H
// These provide the string constants to format data being sent
// the barcode printer for the Code 128 and Data Matrix barcode labels
//////////////////////////////////////////////////////////////////
// Setup Commands
const char SETZPL[] = "^SZ2"; // Set ZPL II code
const char SETDPM[] = "^JMA"; // Set Dots per Millimeter (See ZPL guide for more data)
const char CLRBM[] = "^MCY"; // Clear Bit Map ('Y' = Yes; 'N' = No)
const char PRNTMI[] = "^PMN"; // Print mirror image on label ('Y' = Yes; 'N' = No)
const char PRNTWTH[] = "^PW796"; // Print Width (See ZPL guide for more data)
const char CHNGBF[] = "~JSN"; // Change Backfeed Sequence (Set at Normal -- see ZPL guide for more data)
const char REPRNT[] = "^JZY"; // Reprint after error ('Y' = Yes; 'N' = No)
const char RVSEPRNT[] = "^LRN"; // Label Reverse Print ('Y' = Yes; 'N' = No)
// Universal Commands
const char START[] = "^XA"; // Start of format.
const char HOMEPOS[] = "^LH0,0"; // Set Label Home Position.
const char DATASTART[] = "^FD"; // Start of data
const char DATAEND[] = "^FS"; // End of data
const char END[] = "^XZ"; // End of format.
// Printing Commands for Code 128 Barcodes
const char FONT[] = "^AB,20,10"; // Font selection - LOCKED
const char BCTYPE[] = "^BY1,2.0,30,^BCN,30,N,N,N"; // Set Barcode type (128 w/width modifier);
const char BC1[] = "^FO45,10"; // Set field origin for first barcode
const char BC2[] = "^FO45,85" ; // Set field origin for second barcode
const char BC3[] = "^FO45,155" ; // Set field origin for second barcode
const char ALPHA1[] = "^FO75,50"; // Set field origin for first text
const char ALPHA2[] = "^FO75,120"; // Set field origin for second text
const char ALPHA3[] = "^FO75,190"; // Set field origin for second text
// Printing Commands for Data Matrix Barcodes
const char DMFONT[] = "^A0,30,20"; // Font selection - LOCKED
const char DMTYPE[] = "^BXN,6,200"; // was ^BXN, 5, 200
const char DM1[] = "^FO20,30"; // Set field origin for Data Matrix barc
const char DMTEXT11[] = "^FO250,30"; // Set field origin for first text
const char DMTEXT12[] = "^FO250,65"; // Set field origin for second text
const char DMTEXT13[] = "^FO250,100"; // Set field origin for third text
const char DMTEXT14[] = "^FO250,135"; // Set field origin for fourth text
const char DMTEXT15[] = "^FO250,170"; // Set field origin for fifth text
const char DMTEXT16[] = "^FO250,205"; // Set field origin for sixth text
#include <afx.h>
class CCriticalSection; // Forward Class Declaration (for multithreading)
class Serial; // Forward Declaration (used for RS-232 communications)
struct DataMatrixCoder; // Forward Declaration
class SerialPrinter
{
public:
SerialPrinter(CString portName = "\\\\.\\COM7", UINT baudrate = 115200);
~SerialPrinter();
short DateTimeStamp(); // Prints the current date and time.
short PaperFeed(); // Operates printer paper feed.
short Print(char* string); // Prints data.
// Format data as Code 128 barcode using ZPL II format
short PrintBarcode(char* string);
// Format data as Datamatrix using ZPL II format
short PrintDataMatrix(DataMatrixCoder& dmc);
private:
CCriticalSection* printLock;
Serial* serialPort;
};
Поэтому я добавил дополнительные константы в Константа ЗПЛ.ч и я добавил новый метод в серийный принтер названный ПринтДатаМатрикс для создания и правильного форматирования строки ZPL для печати этикетки Datamatrix
short SerialPrinter::PrintDataMatrix(DataMatrixCoder& dmc)
{
// Lock access to print buffer and printer.
// Used with CCriticalSection.
CSingleLock key(printLock);
key.Lock();
CString dmcString;
dmc.CreateDMCodeString(dmcString);
// Create the ZPL II string
const int ZBUFSIZE = 500;
char zplString[ZBUFSIZE] = "\0";
ostrstream strout(zplString, ZBUFSIZE);
strout << START << SETZPL << SETDPM
<< CLRBM << PRNTMI << PRNTWTH
<< CHNGBF << REPRNT << RVSEPRNT
<< END;
// Datamatrix barcode
strout << START << HOMEPOS << DM1 << DMTYPE
<< DATASTART << dmcString << DATAEND;
// Human-readable text
strout << DMTEXT11 << DMFONT
<< DATASTART << SSNCODE
<< " " << dmc.ssnStr
<< DATAEND;
strout << DMTEXT12 << DMFONT
<< DATASTART << ASNCODE
<< " " << dmc.asnStr
<< DATAEND;
strout << DMTEXT13 << DMFONT
<< DATASTART << REVCODE
<< " " << dmc.prodRev
<< DATAEND;
strout << DMTEXT14 << DMFONT
<< DATASTART << PDATECODE
<< " " << dmc.prodDate
<< DATAEND;
strout << DMTEXT15 << DMFONT
<< DATASTART << MACCODE
<< " " << dmc.macAddress
<< DATAEND;
strout << DMTEXT16 << DMFONT
<< DATASTART << SNCODE
<< " " << dmc.serialNum
<< DATAEND
<< END;
short printReturn = Print(zplString);
// Unlock access to print buffer and printer.
// Used with CCriticalSection.
key.Unlock();
return ;
}
Проблемы, с которыми я столкнулся
Самой сложной частью этого проекта было правильное форматирование и печать удобочитаемого текста. Это заняло довольно много проб и ошибок. Но как только это было завершено, дело было более или менее решено.
Но как только это было сделано, мне пришлось реализовать это решение в LabVIEW.
Для тех, кто не знаком с LabVIEW, это проприетарный язык графического программирования от National Instruments (NI).
Я не большой поклонник LabVIEW. Это не так уж плохо; это просто не в моем вкусе. В основном потому, что я считаю, что это замедляет мой кодинг, а некоторые части просто утомительны.
Print_DM_Address.vi является примером этого.
Как я уже говорил, VI (Virtual Instrument) — это версия функции LabVIEW. Он состоит из двух компонентов — лицевой панели (которая в основном представляет собой пользовательский интерфейс для каждого ВП) и блок-схемы (фактического кода).
В любом случае, вот Print_DM_Address.vi во всей красе:
LabVIEW было бы намного приятнее в использовании, если бы он имел зум особенность — нравится Майкрософт Визио, Автокад, Дизайнер, и т. д. Вы знаете, другие приложения, используемые для создания графического дизайна. Возможно, однажды NI добавит эту функцию. Я говорю это, потому что подключение всех этих проводов к этим коробкам было очень утомительной работой.
Однако, хотите верьте, хотите нет, эта реализация работает точно так же, как реализация Visual C++.
Основные выводы
Я изучил код ZPL II для создания штрих-кодов Data Matrix, и это не заняло много времени. Эта функция заработала в обоих приложениях менее чем за две недели.
Заключительные мысли и следующие шаги
Этот проект был немного сложным из-за нехватки времени, но в то же время это было очень весело.
Руководитель производственного проекта и представитель по маркетингу были очень довольны временем выполнения этой функции.
Этикетка Data Matrix точно соответствовала спецификациям — без использования стороннего программного обеспечения.
И поскольку этикетки Data Matrix станут частью будущих продуктов, я, вероятно, получу много пользы от этого решения.