Функция печати этикеток Data Matrix в C++ и LabView

Обо мне

Я инженер-испытатель на производстве и программист-самоучка на Visual C++.

В качестве инженера-испытателя на производстве моя основная обязанность заключается в разработке специализированного программного и аппаратного обеспечения, которое функционально тестирует продукты моей компании, чтобы гарантировать, что продукты работают в соответствии с ее спецификациями.

Я в основном отвечаю за разработку программного обеспечения и с 2001 года разрабатываю пользовательские приложения для Windows на Visual C++.

Недавно я также начал разрабатывать приложения в LabVIEW. В конце концов руководство распорядилось, чтобы с этого момента это была единственная тестовая программная платформа для стандартизации вместе с нашими европейскими коллегами, которые годами используют LabVIEW.

Проблема, которую я хотел решить

Маркетинг потребовал, чтобы перед отправкой покупателю на линейке продуктов была размещена этикетка Data Matrix на каждом продукте определенного типа модели. На этикетке должен быть как штрих-код Data Matrix, так и читаемый человеком текст.

Образец этикетки DM.png

Информация на этикетке будет уникальной для каждого продукта и будет содержать следующее:

  • Тип модели
  • Версия продукта
  • Код даты
  • 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.

Код 128 штрих-код.png

Итак, я просмотрел 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 во всей красе:
Print_DM_Address Передняя панель.png

Print_DM_Address BD1 .png

Print_DM_Address BD2 .png

LabVIEW было бы намного приятнее в использовании, если бы он имел зум особенность — нравится Майкрософт Визио, Автокад, Дизайнер, и т. д. Вы знаете, другие приложения, используемые для создания графического дизайна. Возможно, однажды NI добавит эту функцию. Я говорю это, потому что подключение всех этих проводов к этим коробкам было очень утомительной работой.

Однако, хотите верьте, хотите нет, эта реализация работает точно так же, как реализация Visual C++.

Основные выводы

Я изучил код ZPL II для создания штрих-кодов Data Matrix, и это не заняло много времени. Эта функция заработала в обоих приложениях менее чем за две недели.

Заключительные мысли и следующие шаги

Этот проект был немного сложным из-за нехватки времени, но в то же время это было очень весело.

Руководитель производственного проекта и представитель по маркетингу были очень довольны временем выполнения этой функции.

Этикетка Data Matrix точно соответствовала спецификациям — без использования стороннего программного обеспечения.

И поскольку этикетки Data Matrix станут частью будущих продуктов, я, вероятно, получу много пользы от этого решения.

Матрица данных 2.png

Похожие записи

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *