/* --- https://radiohlam.ru --- */
//---------------------------------------------------------------------------
#include <vcl.h>
#include <stdlib.h>
#pragma hdrstop

extern "C" {
#include "hidsdi.h"
#include <setupapi.h>
}
#pragma comment(lib,"HID-BCB.LIB")

#include "Unit1.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;
//---------------------------------------------------------------------------
/*
 *  HID Initialization
 *    Parameters:      None
 *    Return Value:    None
 */

void HID_Init()
{	int  i;

	DevCount = 0;																// обнуляем количество найденных устройств
	DevSelected = -1;															// сбрасываем выбранное устройство
	for(i = 0; i < DEV_NUM; i++)												// перебираем весь массив с указателями на структуры, хранящие пути к устройствам
	{	DevDetailData[i] = NULL;												// обнуляем очередной указатель на структуру, хранящую путь к устройству
	}
}
//---------------------------------------------------------------------------
/*
 *  HID Uninitialization
 *    Parameters:      None
 *    Return Value:    None
 */

void HID_UnInit()
{	int  i;

	for(i = 0; i < DEV_NUM; i++)												// перебираем весь массив с указателями на структуры, хранящие пути к устройствам
	{	if(DevDetailData[i])	free(DevDetailData[i]);							// очищаем память структуры, хранящей путь к устройству
	}
}
//---------------------------------------------------------------------------
/*
 *  HID Find Devices
 *    Parameters:      None
 *    Return Value:    Number of Devices found
 */
int HID_FindDevices()
{	GUID								HidGuid;								// идентификатор класса HID-устройств
	HANDLE								DevHandle;								// указатель на открытый девайс
	HDEVINFO							DevInfo;								// указатель на полученный набор (список) HID-устройств
	SP_DEVICE_INTERFACE_DATA			DevData;								// структура с данными об интерфейсе очередного устройства
	PSP_DEVICE_INTERFACE_DETAIL_DATA	DevDetail;								// структура с детальными данными об интерфейсе очередного устройства (содержит путь к устройству)
	PSP_DEVICE_INTERFACE_DETAIL_DATA	DevDetailSelected;						// структура с детальными данными об интерфейсе выбранного устройства (содержит путь к устройству)
	PHIDP_PREPARSED_DATA				PreparsedData;							// блок данных для обработки репортов
	HIDD_ATTRIBUTES						Attributes;								// структура для хранения атрибутов устройства
	HIDP_CAPS							Capabilities;							// возможности из дескриптора репорта
	ULONG								Length;									// размер буфера для получения детальных данных об интерфейсе очередного устройства
	int									Index;									// номер очередного рассматриваемого интерфейса
	int									Sz;										// размер пути
	BOOL								ok;										// признак успешности вызова некоторых функций

	wchar_t								Manufacturer[128];						// сюда будем получать производителя
	wchar_t								Product[128];	  						// сюда - название продукта
	wchar_t 							Serial[128];	  						// а сюда - серийный номер

	// получаем GUID класса устройств USB HID
	HidD_GetHidGuid(&HidGuid);
	Form1->RichEdit1->Lines->Add("HID GUID: " + Sysutils::GUIDToString(HidGuid)); // пишем полученный GUID в Log

	// получаем список всех HID-устройств (то есть устройств с указанным GUID), зарегистрированных в системе
	DevInfo = SetupDiGetClassDevs(	&HidGuid,
									NULL,
									NULL,
									(DIGCF_PRESENT | DIGCF_DEVICEINTERFACE)
								 );
	Form1->RichEdit1->Lines->Add("Ok! Список зарегистрированных в системе HID-устройств получен");	// сообщаем, что список получен
	DevData.cbSize = sizeof(DevData);											// размер структуры, содержащей данные об интерфейсе

	DevDetail = NULL;															// сбрасываем указатель на структуру с детальными данными об интерфейсе
	if (DevSelected != -1)
	{	/* Копируем детальные данные (путь) текущего выбранного девайса во временную переменную (структуру),
			чтобы его можно было найти во вновь сформированном списке */
		DevDetailSelected = (PSP_DEVICE_INTERFACE_DETAIL_DATA) malloc(DevDetailDataSz[DevSelected]);
		memcpy(DevDetailSelected, DevDetailData[DevSelected], DevDetailDataSz[DevSelected]);
	}
	else
	{	DevDetailSelected = NULL;		  										// если текущего выбранного девайса нет - сбрасываем указатель
	}

	// сбрасываем все указатели на детальные данные в списке
	for (Index = 0; Index < DEV_NUM; Index++)
	{	if (DevDetailData[Index])
		{	free(DevDetailData[Index]);											// очищаем структуру (освобождаем память)
			DevDetailData[Index] = NULL;										// сбрасываем указатель
		}
	}

	/* Scan all Devices */
	// готовим ComboBox к составлению нового списка
	Form1->ComboBox1->Items->Clear();											// очищаем существующий список устройств
	//Form1->ComboBox1->Items->Add(UnicodeString("<None>"));						// первый девайс в списке - None

	Index = -1;																	// инициализируем счётчик для перебора всех найденных hid-устройств
	DevCount = 0;																// инициализируем счётчик добавленных в список устройств
	DevHandle = INVALID_HANDLE_VALUE;											// сбрасываем указатель на открытый девайс
	do
	{   Index++;																// увеличиваем счётчик для перебоора всех найденных hid-устройств
		if(DevHandle != INVALID_HANDLE_VALUE)	CloseHandle(DevHandle); 		// закрываем открытый ранее девайс, если почему-то не закрыли до этого

		ok = SetupDiEnumDeviceInterfaces(DevInfo,0,&HidGuid,Index,&DevData);	// получаем информацию об очередном устройстве
		if(!ok)	break;															// видимо больше нет устройств, - завершаем цикл do

		// сначала узнаём какого размера нужен буфер для получения пути к устройству
		ok = SetupDiGetDeviceInterfaceDetail(DevInfo,&DevData,NULL,0,&Length,NULL);

		/* Выделяем память под буфер с детальными данными (путь) */
		if(DevDetail)	free(DevDetail);										// если у нас уже есть буфер, содержащий путь - удаляем его
		DevDetail = (PSP_DEVICE_INTERFACE_DETAIL_DATA) malloc(Length);          // создаём новый буфер нужного размера в виде структуры PSP_DEVICE_INTERFACE_DETAIL_DATA
		Sz = Length;															// запоминаем размер созданной структуры
		DevDetail->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA);			// устанавливаем размер созданной структуры

		// получаем детальную информацию (путь к устройству)
		ok = SetupDiGetDeviceInterfaceDetail(DevInfo,&DevData,DevDetail,Length,NULL,NULL);
		if(!ok)	continue;														// завершаем текущую итерацию и переходим к следующему устройству
		// выводим полученный путь к устройству в log
		Form1->RichEdit1->Lines->Add("Device_" + IntToStr(Index) + ":");
		Form1->RichEdit1->Lines->Add("   |-> path: " + UnicodeString(DevDetail->DevicePath));

		/* проверяем доступность устройства, пытаясь открыть его с параметром dwDesiredAccess = 0 */
		DevHandle = CreateFile(	DevDetail->DevicePath,
								0,                                      		/* dwDesiredAccess */
								FILE_SHARE_READ | FILE_SHARE_WRITE,
								(LPSECURITY_ATTRIBUTES)NULL,
								OPEN_EXISTING,
								0,
								NULL
							  );
		if(DevHandle == INVALID_HANDLE_VALUE)	continue;						// если доступ получить не удалось - завершаем итерацию, переходим к следующему устройству
		// сообщаем, что девайс доступен
		Form1->RichEdit1->Lines->Add("   |-> Устройство доступно");

		Attributes.Size = sizeof(Attributes);	 								// определяем размер структуры для хранения атрибутов устройства
		ok = HidD_GetAttributes(DevHandle,&Attributes);							// получаем атрибуты устройства
		if(!ok) continue; 														// завершаем текущую итерацию и переходим к следующему устройству
		// выводим в лог атрибуты девайса
		Form1->RichEdit1->Lines->Add("   |-> VID = " + IntToHex(Attributes.VendorID,4) +
									" & PID = " + IntToHex(Attributes.ProductID,4));

		ok = HidD_GetManufacturerString(DevHandle, Manufacturer, 128);	 		// получаем производителя
		if(!ok)
		{	Form1->RichEdit1->Lines->Add("   |-> Manufacturer: Unavailable");	// сообщаем, что производитель недоступен
			wcscpy(Manufacturer, IntToHex(Attributes.VendorID,4).w_str());		// прописываем вместо производителя VID
		}
		else	Form1->RichEdit1->Lines->Add("   |-> Manufacturer: " + UnicodeString(Manufacturer));	// выводим в лог производителя

		ok = HidD_GetProductString(DevHandle, Product, 128);	  				// получаем имя продукта
		if(!ok)
		{	Form1->RichEdit1->Lines->Add("   |-> Product: Unavailable"); 		// сообщаем, что имя продукта недоступно
			wcscpy(Product, IntToHex(Attributes.ProductID,4).w_str());			// прописываем вместо имени продукта PID
		}
		else	Form1->RichEdit1->Lines->Add("   |-> Product: " + UnicodeString(Product));	// выводим в лог имя продукта

		ok = HidD_GetSerialNumberString(DevHandle, Serial, 128);				// получаем серийный номер продукта
		if(!ok) Form1->RichEdit1->Lines->Add("   |-> Serial: Unavailable");    				// сообщаем, что номер недоступен
		else	Form1->RichEdit1->Lines->Add("   |-> Serial: " + UnicodeString(Serial));	// сообщаем серийный номер продукта

		ok = HidD_GetPreparsedData(DevHandle, &PreparsedData);					// запрашиваем блок данных для обработки репортов
		if(!ok)	continue;														// завершаем текущую итерацию и переходим к следующему устройству

		ok = HidP_GetCaps(PreparsedData, &Capabilities);   						// получаем возможности из дескриптора репорта
		if(!ok) continue;								   						// завершаем текущую итерацию и переходим к следующему устройству
		HidD_FreePreparsedData(PreparsedData);									// освобождаем ресурсы, которые выделил драйвер класса HID для хранения блока данных о репортах
		/* выводим возможности (USage, UsagePage, размеры репортов IN, OUT, FEATURE) в лог*/
		Form1->RichEdit1->Lines->Add("   |-> Usage = " + IntToHex(Capabilities.Usage,4));
		Form1->RichEdit1->Lines->Add("   |-> Usage Page = " + IntToHex(Capabilities.UsagePage,4));
		Form1->RichEdit1->Lines->Add("   |-> InputReportByteLength = " + IntToHex(Capabilities.InputReportByteLength,4));
		Form1->RichEdit1->Lines->Add("   |-> OutputReportByteLength = " + IntToHex(Capabilities.OutputReportByteLength,4));
		Form1->RichEdit1->Lines->Add("   |-> FeatureReportByteLength = " + IntToHex(Capabilities.FeatureReportByteLength,4));

		/* запоминаем в массив детальную информацию (путь к устройству) о поддерживаемых девайсах */
		//if((Capabilities.UsagePage == 0xFF00) && (Capabilities.Usage == 0x0001))	// идентификация устройства по Usage и UsagePage
		//{   // добавляем очередной девайс в ComboBox
			// если у девайса вообще нет ни имени, ни производителя, то вместо них мы прописали VID/PID
			Form1->ComboBox1->Items->Add(UnicodeString(Manufacturer) + UnicodeString(" - ") + UnicodeString(Product));
			DevDetailData[DevCount] = DevDetail;								// сохраняем путь к устройству
			DevDetailDataSz[DevCount] = Sz;										// сохраняем размер пути
			// нулевой байт - это всегда report ID, поэтому у нас всегда длина репортов на 1 байт больше, чем количество полезных байт
			DevInputReportSz[DevCount] = Capabilities.InputReportByteLength;	// сохраняем размер репорта IN устройства
			DevOutputReportSz[DevCount] = Capabilities.OutputReportByteLength;	// сохраняем размер репорта OUT устройства
			DevFeatureReportSz[DevCount] = Capabilities.FeatureReportByteLength;// сохраняем размер репорта Feature устройства
			if (DevDetailSelected)												// если есть какой-то выбранный девайс
			{	// если строки равны (результат сравнения = 0)
				if (!strcmp(DevDetailSelected->DevicePath, DevDetailData[DevCount]->DevicePath))
				{	DevSelected = DevCount;										// обозначаем текущий девайс как выбранный
					Form1->ComboBox1->ItemIndex = DevCount;						// выбираем его в ComboBox
				}
			}
			DevCount++;															// увеличиваем счётчик
			DevDetail = NULL;													// сбрасываем указатель на структуру с детальными данными
		//}
		CloseHandle (DevHandle); 												// закрываем открытый ранее девайс
		DevHandle = INVALID_HANDLE_VALUE;										// сбрасываем указатель
	} while(DevCount < DEV_NUM);												// максимальное количество устройств задефайнено в хидере

  if (DevDetail)			free(DevDetail);	   								// удаляем буфер, содержащий путь к последнему найденному устройству
  if (DevDetailSelected)	free(DevDetailSelected);							// удаляем буфер, в котором запоминали путь к выбранному устройству
  SetupDiDestroyDeviceInfoList(DevInfo);										// удаляем буфер, содержащий список HID-устройств

  return (DevCount);															// возвращаем количество найденных устройств
}
//---------------------------------------------------------------------------
/*
 *  HID Open
 *    Parameters:      num:   Device number (из списка, сформированного при поиске)
 *    Return Value:    TRUE - Success, FALSE - Error
 */
BOOL HID_Open(int Num)
{	ULONG	NumberBuffers;														// размер буфера

	if (DevDetailData[Num] == NULL) return (FALSE);								// если в списке путей к устройствам нет пути к указанному девайсу - выходим и возвращаем false

	/* открываем устройство для чтения/записи в режиме общего доступа */
	DevHandle = CreateFile(	DevDetailData[Num]->DevicePath,
							GENERIC_READ | GENERIC_WRITE,
							FILE_SHARE_READ | FILE_SHARE_WRITE,
							(LPSECURITY_ATTRIBUTES)NULL,
							OPEN_EXISTING,
							FILE_FLAG_OVERLAPPED,
							NULL
						  );
	if(DevHandle == INVALID_HANDLE_VALUE) return (FALSE);	   					// если не получилось открыть - выходим и возвращаем false
	DevSelected = Num;															// назначаем указанный девайс в качестве выбранного
	memset(&DevWriteOverlapped, 0, sizeof(OVERLAPPED));	   						// обнуляем данные в структуре OVERLAPPED для записи
	memset(&DevReadOverlapped, 0, sizeof(OVERLAPPED));	   						// обнуляем данные в структуре OVERLAPPED для чтения
	// узнаём размер приёмного буфера
	if(HidD_GetNumInputBuffers(DevHandle, &NumberBuffers))						// пытаемся узнать размер буфера
	{	Form1->RichEdit1->Lines->Add("Размер приёмного буфера: " + IntToStr((int)NumberBuffers));	// выводим в лог количество найденных устройств
	}
	else
	{	Form1->RichEdit1->Lines->Add("Не удалось узнать размер приёмного буфера!");	// выводим в лог количество найденных устройств
	}
	return (TRUE);
}
//---------------------------------------------------------------------------
/*
 *  HID Close
 *    Parameters:      None
 *    Return Value:    None
 */
void HID_Close()
{	DevSelected = -1;															// отменяем выбор устройства
	CancelIo(DevHandle);														// отменяем все асинхронные операции для устройства
	DevReadPending  = FALSE;

	if(DevHandle != INVALID_HANDLE_VALUE)
	{	CloseHandle(DevHandle);
		DevHandle = INVALID_HANDLE_VALUE;
	}
}
//---------------------------------------------------------------------------
/*
 *  HID Write
 *    Parameters:      buf:   Pointer to buffer with data to write
 *                     sz:    Number of bytes to write
 *                     cnt:   Pointer to number of bytes written
 *    Return Value:    TRUE - Success, FALSE - Error
 */

BOOL HID_Write(BYTE *Buf, DWORD Sz, DWORD *Cnt)
{	WriteFile(DevHandle, Buf, Sz, Cnt, &DevWriteOverlapped);					// асинхронно отправляем буфер в устройство
	WaitForSingleObject(DevWriteOverlapped.hEvent,2000);        				// приостановить поток на 2 с или пока не завершится операция записи в порт
	if(GetOverlappedResult(DevHandle,&DevWriteOverlapped,Cnt,true))	return true;// если операция завершилась успешно - вернуть истину
	else                                                                        // в противном случае -
	{	CancelIo(DevHandle);													// отменяем асинхронные операции ввода/вывода
		return false; 															// возвращаем ложь
	}
}
//---------------------------------------------------------------------------
/*
 *  HID Read
 *    Parameters:      buf:   Pointer to buffer that receives data
 *                     sz:    Number of bytes to read
 *                     cnt:   Pointer to number of bytes read
 *    Return Value:    TRUE - Success, FALSE - Error
 */

BOOL HID_Read(BYTE *Buf, DWORD Sz, DWORD *Cnt)
{	int last_err;																// сюда будем получать код ошибки

	if (!DevReadPending)														// если флаг ожидания завершения операции чтения сброшен
	{	if (ReadFile(DevHandle, Buf, Sz, Cnt, &DevReadOverlapped))				// пытаемся асинхронно прочитать из устройства IN репорт
		{	return (true);														// если получилось - выходим, возвращаем true, Cnt = количество прочитанных байт
		}
		DevReadPending = true;													// устанавливаем флаг ожидания завершения операции чтения
	}
	else																		// если флаг ожидания завершения операции чтения установлен
	{	if (GetOverlappedResult(DevHandle, &DevReadOverlapped, Cnt, false))		// проверяем, завершилась ли операция чтения
		{	DevReadPending  = false;											// сбрасываем флаг ожидания завершения операции чтения
																				// Cnt содержит количество принятых байт
			return (true);														// выходим, возвращаем true
		}
	}
	// сюда попадаем либо если только что установили флаг ожидания, либо если флаг установлен ранее,
	// но проверка показала, что операция чтения данных ещё не завершилась (данные ещё не приняты)
	last_err = GetLastError();													// узнаём код последней ошибки
	if((last_err != ERROR_IO_INCOMPLETE) && (last_err != ERROR_IO_PENDING))		// если ошибка заключается не в том, что данные просто до конца не приняты
	{	DevReadPending  = false;												// сбрасываем флаг ожидания завершения операции чтения
		CancelIo(DevHandle);													// отменяем все асинхронные операции ввода/вывода (но девайс не закрываем)
		return (false);															// выходим, возвращаем false
	}
	return (true);																// если данные  просто ещё не приняты до конца - выходим, возвращаем true
}
//---------------------------------------------------------------------------
//--- выполняется при наличии прочитанных данных ----------------------------
void OnInput()
{	UnicodeString	ComposedStr;												// здесь будем формировать строку вывода

	ComposedStr = "";															// сначала строка пустая
	for(int i=1; i<InReportSz; i++)												// перебираем все полезные байты репорта
	{	ComposedStr = ComposedStr + " " + IntToHex(InReport[i],2);				// преобразуем очередной байт в строковое представление hex
	}
	Form1->RichEdit1->Lines->Add("Recieved data (input):" + ComposedStr);		//  выводим сформированную строку в лог
}
//---------------------------------------------------------------------------
//--- выполняется при ошибке во время чтения / записи данных ----------------
void OnError()
{	Running = FALSE;															// сбрасываем флаг открытого устройства
	Form1->Timer1->Enabled = false;												// выключаем таймер
	Form1->RichEdit1->Lines->Add("Периодический опрос остановлен");	  			// сообщаем что периодический опрос остановлен (чтение из усторйства)
	HID_Close();																// закрываем открытое устройство
	Form1->RichEdit1->Lines->Add("Устройство закрыто");							// сообщаем что устройство закрыто
}
//---------------------------------------------------------------------------
//***************************************************************************
//--- FORM FUNCTIONS --------------------------------------------------------
//--- конструктор формы -----------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
	: TForm(Owner)
{   Form1->AutoSize = true;	 													// устанавливаем авторазмер формы
	Form1->Panel1->AutoSize = true;	  											// устанавливаем авторазмер формы и её главной панели
	// инициализация USB
	Running       = FALSE;														// нет подключенных устройств
	InReport      = NULL;														// указатели на буферы для приёма/передачи сброшены
	OutReport     = NULL;
	FeatureReport = NULL;
	HID_Init();																	// инициализируем переменные и структуры для поиска HID-устройств
	Form1->RichEdit1->Lines->Add("Выполняется поиск HID-устройств...");			// сообщаем о начале поиска HID-устройств
	DevCount = HID_FindDevices();   	                                        // выполняем поиск HID-устройств
	Form1->RichEdit1->Lines->Add("Найдено устройств: "+IntToStr(DevCount));		// выводим в лог количество найденных устройств
}
//---------------------------------------------------------------------------
//--- меню -> exit ----------------------------------------------------------
void __fastcall TForm1::Exit1Click(TObject *Sender)
{   if (Running)
	{	Running = FALSE;														// снимаем флаг наличия подключенного устройства
		HID_Close();															// отменяем асинхронные операции I/O и закрываем устройство
	}
	if (InReport)      free(InReport);											// очищаем буферы репортов
	if (OutReport)     free(OutReport);
	if (FeatureReport) free(FeatureReport);
	HID_UnInit();																// очищаем структуры, содержащие пути к устройствам
	Close();																	// закрываем приложение
}
//---------------------------------------------------------------------------
//--- изменили выбранный девайс ---------------------------------------------
void __fastcall TForm1::ComboBox1Change(TObject *Sender)
{	Form1->RichEdit1->Lines->Add("Выбрано устройство: "+IntToStr(Form1->ComboBox1->ItemIndex));
}
//---------------------------------------------------------------------------
//--- кнопка "Clear log" ----------------------------------------------------
void __fastcall TForm1::Button4Click(TObject *Sender)
{	Form1->RichEdit1->Clear();													// очищаем лог
}
//---------------------------------------------------------------------------
//--- кнопка Find USB HID Devices -------------------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{   Form1->RichEdit1->Lines->Add(""); 											// перевод каретки
	Form1->RichEdit1->Lines->Add("Выполняется поиск HID-устройств...");			// сообщаем о начале поиска HID-устройств
	DevCount = HID_FindDevices();												// выполняем поиск HID-устройств
	Form1->RichEdit1->Lines->Add("Найдено устройств: "+IntToStr(DevCount));		// выводим в лог количество найденных устройств
}
//---------------------------------------------------------------------------
//--- кнопка Connect --------------------------------------------------------
void __fastcall TForm1::Button2Click(TObject *Sender)
{	if(Running)																	// если есть открытый девайс сообщаем, что его нужно закрыть
	{   Form1->RichEdit1->Lines->Add("Сначала нужно закрыть открытое устройство");
	}
	else																		// если открытого девайса нет
	{	if(Form1->ComboBox1->ItemIndex != -1)									// если что-то выбрано
		{   // пробуем открыть выбранное устройство
			DevSelected = Form1->ComboBox1->ItemIndex;							// получаем индекс устройства в списке
			if (HID_Open(Form1->ComboBox1->ItemIndex))							// если открылось
			{   // выводим в лог путь к открытому устройству
				Form1->RichEdit1->Lines->Add("Открыто устройство "+UnicodeString(DevDetailData[DevSelected]->DevicePath));
				// заполняем размеры репортов из сохранённых при поиске массивов
				InReportSz		= DevInputReportSz[DevSelected];
				OutReportSz     = DevOutputReportSz[DevSelected];
				FeatureReportSz = DevFeatureReportSz[DevSelected];
				// очищаем буферы репортов
				if (InReport)      free(InReport);
				if (OutReport)     free(OutReport);
				if (FeatureReport) free(FeatureReport);
				// выдеяем память под новые буферы репортов в соответствии с требуемыми размерами
				InReport		= (BYTE *)calloc(InReportSz, 1);
				OutReport       = (BYTE *)calloc(OutReportSz, 1);
				FeatureReport   = (BYTE *)calloc(FeatureReportSz, 1);
				Running          = true;										// устанавливаем флаг наличия открытого устройства
			}
			else
			{	// сообщаем, что не удалось открыть нужное устройство
				Form1->RichEdit1->Lines->Add("Не удалось открыть устройство "+UnicodeString(DevDetailData[DevSelected]->DevicePath));
				DevSelected = -1;												// сбрасываем индекс
			}
		}
		else                                                                    // если ничего не выбрано
		{	Form1->RichEdit1->Lines->Add("Сначала нужно выбрать устройство");	// сообщаем что сначала нужно выбрать
		}
	}
}
//---------------------------------------------------------------------------
//--- кнопка Disconnect -----------------------------------------------------
void __fastcall TForm1::Button3Click(TObject *Sender)
{	if(Running)																	// если есть открытый девайс - закрываем его
	{	Running = false;														// сбрасываем флаг наличия открытого устройства
		Form1->Timer1->Enabled = false;											// выключаем таймер
		Form1->RichEdit1->Lines->Add("Периодический опрос остановлен");	  		// сообщаем что периодический опрос остановлен (чтение из усторйства)
		HID_Close();                                                            // закрываем открытое устройство
		Form1->RichEdit1->Lines->Add("Устройство закрыто");						// сообщаем что устройство закрыто
	}
	else
	{	Form1->RichEdit1->Lines->Add("Нет подключенных устройств");				// сообщаем, что нет открытых устройств
    }
}
//---------------------------------------------------------------------------
//--- ввод (любая кнопка) в поле ввода данных для отправки ------------------
void __fastcall TForm1::Edit1KeyPress(TObject *Sender, wchar_t &Key)
{	wchar_t *CompetentChars=L"0123456789abcdefABCDEF";	   						// набор допустимых символов (UTF)
	wchar_t AddStrWChar[2]={0,0};  												// для преобразования символа в юникод-строку
	UnicodeString	SourceStrBegin;												// исходная строка до начала выделения
	UnicodeString	SourceStrEnd;												// исходная строка после конца выделения
	UnicodeString	AddStrUC;													// добавочная строка (из нашего символа)
	int				SelStartPos;												// позиция начала выделения
	int				SelEndPos;													// позиция конца выделения
	int				EndPartSize;												// размер куска после выделения
	int				SpaceIndex;													// индекс пробела в строке
	int				SpaceNumber;												// количество добавляемых пробелов
	int				SpaceCounter;												// счётчик пробелов

	SelStartPos = Form1->Edit1->SelStart;										// запоминаем начало выделения (позицию)

	if(wcschr(CompetentChars, Key) != 0)									 	// если нажали клавишу из набора
	{   SelEndPos = SelStartPos + Form1->Edit1->SelLength;						// запоминаем конец выделения
		EndPartSize = (Form1->Edit1->Text).Length() - SelEndPos;   				// вычисляем размер куска после выделения

		AddStrWChar[0] = Key;  					 								// сохраняем Key для преобразования
		AddStrUC = UnicodeString(AddStrWChar);									// преобразуем Key в юникод-строку
		if(SelStartPos%3 == 2)        	                	                    // если мы в позиции перед пробелом, то
		{	AddStrUC = L" "+AddStrUC; 											// вставляем пробел перед введённым символом
		}

		SourceStrBegin = (Form1->Edit1->Text).SubString(1,SelStartPos);			// получаем начало исходной строки (до выделения)
		SourceStrEnd = (Form1->Edit1->Text).SubString(SelEndPos+1,EndPartSize);	// получаем конец исходной строки (после выделения)

		// удаляем все пробелы из конца строки
		while((SpaceIndex = SourceStrEnd.Pos(L" "))!=0)				  			// выполнять, пока пробелы есть (индекс не равен нулю)
		{	SourceStrEnd = SourceStrEnd.Delete(SpaceIndex,1);		  			// удаляем очередной пробел
		}

		SpaceIndex = 1 + 2 - (SelStartPos + AddStrUC.Length())%3;  			   	// вычисляем остаток от деления на 3 новой позиции курсора и в зависимости от этого
																				// определяем, перед символами в каких позициях нужно вставлять пробелы в конце строки
																				// 1 впереди добавлен из-за того, что нас в методе Insert интересует не позиция курсора,
																				// в кторую нужно вставить символ, а позиция символа, перед которым нужно вставить символ
		// теперь нужно расставить пробелы в конце строки заново
		SpaceNumber = (SourceStrEnd.Length()+1)/2;								// количество добавляемых пробелов = целой части от (SourceStrEnd+1) / 2
		SpaceCounter = 0;														// сбрасываем счётчик добавленных пробелов
		while(SpaceCounter<SpaceNumber)											// выполнять, пока не вставим нужное количество пробелов
		{	SourceStrEnd.Insert(L" ",SpaceIndex+SpaceCounter*3);				// вставляем очередной пробел в нужную позицию
			SpaceCounter++;
		}
		SourceStrEnd = SourceStrEnd.TrimRight();								// убираем лишний пробел в конце, если он появился

		Form1->Edit1->Text = (SourceStrBegin + AddStrUC + SourceStrEnd).UpperCase();	// формируем новую строку и вписываем её вместо старой
		// выставляем курсор на место
		Form1->Edit1->SelStart = SelStartPos + AddStrUC.Length(); 				// выставляем курсор на новое место, в зависимости от того, сколько мы добавляли символов
		Key = 0;																// сам введённый символ больше не нужен
	}
	else	Key = 0; 															// отменяем ввод
}
//---------------------------------------------------------------------------
//--- ввод в поле ввода данных для отправки (backspase, del и insert) -------
//--- в этом обработчике коды не зависят от выбранной локали ----------------
//--- скажем, если нажать a(ф) при включенном русском - сюда прилетит 0x41, -
//--- а в Edit1KeyPressed - 0x444 -------------------------------------------
void __fastcall TForm1::Edit1KeyDown(TObject *Sender, WORD &Key, TShiftState Shift)
{   UnicodeString	SourceStrBegin;												// исходная строка до начала выделения
	UnicodeString	SourceStrEnd;												// исходная строка после конца выделения
	int				SelStartPos;												// позиция начала выделения
	int				SelEndPos;													// позиция конца выделения
	int				EndPartSize;												// размер куска после выделения
	int				SpaceIndex;													// индекс пробела в строке
	int				SpaceNumber;												// количество добавляемых пробелов
	int				SpaceCounter;												// счётчик пробелов

	switch(Key)
	{   case VK_BACK:
            SelStartPos = Form1->Edit1->SelStart;						   		// запоминаем начало выделения (позицию)
			SelEndPos = SelStartPos + Form1->Edit1->SelLength;			   		// запоминаем конец выделения
			EndPartSize = (Form1->Edit1->Text).Length() - SelEndPos;	   		// вычисляем размер куска после выделения

			if(SelStartPos != 0 && SelStartPos == SelEndPos)					// если ничего не выделено и позиция конца выделения не равна нулевой позиции
			{	SelStartPos -=1;   						 						// удаляем символ перед start
			}

			SourceStrBegin = (Form1->Edit1->Text).SubString(1,SelStartPos);		// получаем начало исходной строки (до выделения)
			SourceStrEnd = (Form1->Edit1->Text).SubString(SelEndPos+1,EndPartSize);	// получаем конец исходной строки (после выделения)

			// удаляем все пробелы из конца строки
			while((SpaceIndex = SourceStrEnd.Pos(L" "))!=0)			  			// выполнять, пока пробелы есть (индекс не равен нулю)
			{	SourceStrEnd = SourceStrEnd.Delete(SpaceIndex,1);	  			// удаляем очередной пробел
			}

			SpaceIndex = 1 + 2 - (SelStartPos)%3;  							   	// вычисляем остаток от деления на 3 новой позиции курсора и в зависимости от этого
																				// определяем, перед символами в каких позициях нужно вставлять пробелы в конце строки
																				// 1 впереди добавлен из-за того, что нас в методе Insert интересует не позиция курсора,
																				// в кторую нужно вставить символ, а позиция символа, перед которым нужно вставить символ
			// теперь нужно расставить пробелы в конце строки заново
			SpaceNumber = (SourceStrEnd.Length()+1)/2;		  					// количество добавляемых пробелов = целой части от (SourceStrEnd+1) / 2
			SpaceCounter = 0;											 		// сбрасываем счётчик добавленных пробелов
			while(SpaceCounter<SpaceNumber)								 		// выполнять, пока не вставим нужное количество пробелов
			{	SourceStrEnd.Insert(L" ",SpaceIndex+SpaceCounter*3);	 		// вставляем очередной пробел в нужную позицию
				SpaceCounter++;
			}
			SourceStrEnd = SourceStrEnd.TrimRight();					 		// убираем лишний пробел в конце, если он появился

			Form1->Edit1->Text = (SourceStrBegin + SourceStrEnd).UpperCase();	// формируем новую строку и вписываем её вместо старой
			// выставляем курсор на место
			Form1->Edit1->SelStart = SelStartPos; 							  	// выставляем курсор на новое место, в зависимости от того, сколько мы добавляли символов
			Key = 0;														  	// сам введённый символ больше не нужен
			break;
		case VK_DELETE:                                                         // обработчик для delete
			SelStartPos = Form1->Edit1->SelStart;						   		// запоминаем начало выделения (позицию)
			SelEndPos = SelStartPos + Form1->Edit1->SelLength;			   		// запоминаем конец выделения
			EndPartSize = (Form1->Edit1->Text).Length() - SelEndPos;	   		// вычисляем размер куска после выделения

			if(SelEndPos != Form1->Edit1->Text.Length() && SelStartPos == SelEndPos)	// если ничего не выделено и позиция конца выделения не равна последней позиции
			{	SelEndPos +=1;							 						// удаляем символ после del
			}

			SourceStrBegin = (Form1->Edit1->Text).SubString(1,SelStartPos);		// получаем начало исходной строки (до выделения)
			SourceStrEnd = (Form1->Edit1->Text).SubString(SelEndPos+1,EndPartSize);	// получаем конец исходной строки (после выделения)

			// удаляем все пробелы из конца строки
			while((SpaceIndex = SourceStrEnd.Pos(L" "))!=0)			  			// выполнять, пока пробелы есть (индекс не равен нулю)
			{	SourceStrEnd = SourceStrEnd.Delete(SpaceIndex,1);	  			// удаляем очередной пробел
			}

			SpaceIndex = 1 + 2 - (SelStartPos)%3;  							   	// вычисляем остаток от деления на 3 новой позиции курсора и в зависимости от этого
																				// определяем, перед символами в каких позициях нужно вставлять пробелы в конце строки
																				// 1 впереди добавлен из-за того, что нас в методе Insert интересует не позиция курсора,
																				// в кторую нужно вставить символ, а позиция символа, перед которым нужно вставить символ
			// теперь нужно расставить пробелы в конце строки заново
			SpaceNumber = (SourceStrEnd.Length()+1)/2;		  					// количество добавляемых пробелов = целой части от (SourceStrEnd+1) / 2
			SpaceCounter = 0;											 		// сбрасываем счётчик добавленных пробелов
			while(SpaceCounter<SpaceNumber)								 		// выполнять, пока не вставим нужное количество пробелов
			{	SourceStrEnd.Insert(L" ",SpaceIndex+SpaceCounter*3);	 		// вставляем очередной пробел в нужную позицию
				SpaceCounter++;
			}
			SourceStrEnd = SourceStrEnd.TrimRight();					 		// убираем лишний пробел в конце, если он появился

			Form1->Edit1->Text = (SourceStrBegin + SourceStrEnd).UpperCase();	// формируем новую строку и вписываем её вместо старой
			// выставляем курсор на место
			Form1->Edit1->SelStart = SelStartPos; 							  	// выставляем курсор на новое место, в зависимости от того, сколько мы добавляли символов
			Key = 0;														  	// сам введённый символ больше не нужен
			break;
		case VK_INSERT:
			Key = 0;															// отменяем insert
			break;
		default:
			break;
	}
}
//---------------------------------------------------------------------------
//--- кнопка Send -----------------------------------------------------------
void __fastcall TForm1::Button5Click(TObject *Sender)
{	DWORD 			ByteCnt;   													// счётчик переданных байт
	AnsiString		SendStr;													// сама строка (в ANSI, чтобы по 1 байту на символ)
	AnsiString		HexAnsiStr;													// кусок с очередным Hex-числом
	int				SpaceIndex;													// индекс пробела в строке
	int				RemAfterDiv;												// отстаток от деления количества байт для передачи на количества полезных байт, посылаемых за 1 раз
	int				PacketsCounter;												// счётчик отправленных пакетов
	int				PacketsNum;													// общее количество пакетов, которые нужно отправить
	char 			*HexStr;													// здесь будем хранить строку с hex
	char			BinBuf[2];													// здесь окажется результат преобразования HexToBin
	UnicodeString	ComposedStr;												// здесь будем формировать строку вывода

	if(Running)
	{	SendStr = AnsiString(Form1->Edit1->Text);  								// получаем текст в виде Ansi-строки
		// удаляем все пробелы
		while((SpaceIndex = SendStr.Pos(" "))!=0)		 			  			// выполнять, пока пробелы есть (индекс не равен нулю)
		{	SendStr = SendStr.Delete(SpaceIndex,1);		 			  			// удаляем очередной пробел
		}
		RemAfterDiv = SendStr.Length()%(OutReportSz - 1);		   				// узнаём, кратен ли размер посылки количеству полезных байт в одном OUT-репорте
		PacketsNum = SendStr.Length()/(2*(OutReportSz - 1));	   				// узнаём количество пакетов для отправки (на 2 делим, поскольку 1 байт кодируется двумя символами)
		if(RemAfterDiv!=0)										   				// если данные не кратны 2*(размер репорта - 1)
		{	Form1->RichEdit1->Lines->Add("Количество данных для передачи не кратно количеству полезных данных репорта OUT.");
			Form1->RichEdit1->Lines->Add("Неполный репорт будет обрезан, последние данные не будут отправлены");	// сообщаем, что обрежем последний репорт
		}
		// организуем цикл для отправки
		PacketsCounter = 0;					 									// сбрасываем счётчик отправленных пакетов
		while(PacketsCounter < PacketsNum)	 									// пока не отправим все пакеты
		{	OutReport[0] = 0;			 	 									// report ID (нулевой байт в репорте - это всегда report ID)
			for(int i=1; i<OutReportSz; i++) 									// заполняем буфер очередного OUT-репорта
			{   HexAnsiStr = SendStr.SubString(PacketsCounter*(OutReportSz-1)*2+(i-1)*2+1,2);	// получаем очередной кусочек строки с hex-числом
				HexStr = HexAnsiStr.c_str(); 									// преобразуем в char*
				HexToBin(HexStr,BinBuf,1);	 									// преобразуем строку char* в число
				OutReport[i] = BinBuf[0];	 									// вписываем очередные данные для передачи
			}
			if(HID_Write(OutReport, OutReportSz, &ByteCnt))	 	   				// если данные успешно отправлены
			{	ComposedStr = "";	  											// сначала строка пустая
				for(int i=1; i<OutReportSz; i++)   								// перебираем все полезные байты репорта
				{	ComposedStr = ComposedStr + " " + IntToHex(OutReport[i],2);	// преобразуем очередной байт в строковое представление hex
				}
				Form1->RichEdit1->Lines->Add("Отправлен пакет №" + IntToHex(PacketsCounter,2));	// сообщаем, что мы отправили пакет
				Form1->RichEdit1->Lines->Add("Sent data:" + ComposedStr);		//  выводим сформированную строку в лог
			}
			else
			{   Form1->RichEdit1->Lines->Add("Не удалось отправить пакет №" + IntToHex(PacketsCounter,2));	// сообщаем, что мы не отправили пакет
				OnError();														// запускаем функцию OnError
				return;		 													// завершаем операцию
			}
			PacketsCounter++;
		}
	}
	else
	{    Form1->RichEdit1->Lines->Add("Нет подключенных устройств");			// сообщаем, что нет открытых устройств
	}
}
//---------------------------------------------------------------------------
//--- функция по таймеру пытается читать данные из устройства (каждую 1 сек)
void __fastcall TForm1::Timer1Timer(TObject *Sender)
{	DWORD Cnt;																	// сюда будем получать количество принятых байт

	if(Running)																	// если устройство подключено
	{	if(HID_Read(InReport, InReportSz, &Cnt))								// если чтение не завершилось ошибкой
		{	if(Cnt)	OnInput();		                                            // если есть принятые данные - выполняем OnInput
		}
		else																	// если чтение завершилось ошибкой
		{	OnError();															// запускаем функцию OnError
		}
	}
	else
	{	Form1->RichEdit1->Lines->Add("Нет подключенных устройств");				// сообщаем, что нет открытых устройств
	}
}
//---------------------------------------------------------------------------
//--- запускаем таймер для периодического чтения данных из буфера -----------
void __fastcall TForm1::Button6Click(TObject *Sender)
{   if(Running)
	{	Form1->Timer1->Enabled = true;											// включаем таймер (периодическое чтение)
		Form1->RichEdit1->Lines->Add("Периодический опрос запущен");	   		// сообщаем что запущен периодический опрос (чтение из устройства)
	}
	else
	{   Form1->RichEdit1->Lines->Add("Нет подключенных устройств");				// сообщаем, что нет открытых устройств
	}
}
//---------------------------------------------------------------------------
//--- останавливаем таймер для периодического чтения данных из буфера -------
void __fastcall TForm1::Button7Click(TObject *Sender)
{	Form1->Timer1->Enabled = false;												// выключаем таймер
	Form1->RichEdit1->Lines->Add("Периодический опрос остановлен");	  			// сообщаем что периодический опрос остановлен (чтение из устройства)
}
//---------------------------------------------------------------------------
//--- чтение с помощью функции HidD_GetInputReport (в обход буфера драйвера)
void __fastcall TForm1::Button9Click(TObject *Sender)
{	if(Running)																	// если устройство подключено
	{	if(HidD_GetInputReport(DevHandle, InReport, InReportSz))		 		// если чтение не завершилось ошибкой
		{	OnInput();				                                            // если есть принятые данные - выполняем OnInput
		}
		else																	// если чтение завершилось ошибкой
		{	OnError();															// запускаем функцию OnError
		}
	}
	else
	{	Form1->RichEdit1->Lines->Add("Нет подключенных устройств");				// сообщаем, что нет открытых устройств
	}
}
//---------------------------------------------------------------------------
//--- отправка с помощью функции HidD_SetOuputReport ------------------------
void __fastcall TForm1::Button10Click(TObject *Sender)
{   DWORD 			ByteCnt;   													// счётчик переданных байт
	AnsiString		SendStr;													// сама строка (в ANSI, чтобы по 1 байту на символ)
	AnsiString		HexAnsiStr;													// кусок с очередным Hex-числом
	int				SpaceIndex;													// индекс пробела в строке
	int				RemAfterDiv;												// отстаток от деления количества байт для передачи на количества полезных байт, посылаемых за 1 раз
	int				PacketsCounter;												// счётчик отправленных пакетов
	int				PacketsNum;													// общее количество пакетов, которые нужно отправить
	char 			*HexStr;													// здесь будем хранить строку с hex
	char			BinBuf[2];													// здесь окажется результат преобразования HexToBin
	UnicodeString	ComposedStr;												// здесь будем формировать строку вывода

	if(Running)
	{	SendStr = AnsiString(Form1->Edit1->Text);  								// получаем текст в виде Ansi-строки
		// удаляем все пробелы
		while((SpaceIndex = SendStr.Pos(" "))!=0)		 			  			// выполнять, пока пробелы есть (индекс не равен нулю)
		{	SendStr = SendStr.Delete(SpaceIndex,1);		 			  			// удаляем очередной пробел
		}
		RemAfterDiv = SendStr.Length()%(OutReportSz - 1);		   				// узнаём, кратен ли размер посылки количеству полезных байт в одном OUT-репорте
		PacketsNum = SendStr.Length()/(2*(OutReportSz - 1));	   				// узнаём количество пакетов для отправки (на 2 делим, поскольку 1 байт кодируется двумя символами)
		if(RemAfterDiv!=0)										   				// если данные не кратны 2*(размер репорта - 1)
		{	Form1->RichEdit1->Lines->Add("Количество данных для передачи не кратно количеству полезных данных репорта OUT.");
			Form1->RichEdit1->Lines->Add("Неполный репорт будет обрезан, последние данные не будут отправлены");	// сообщаем, что обрежем последний репорт
		}
		// организуем цикл для отправки
		PacketsCounter = 0;					 									// сбрасываем счётчик отправленных пакетов
		while(PacketsCounter < PacketsNum)	 									// пока не отправим все пакеты
		{	OutReport[0] = 0;			 	 									// report ID (нулевой байт в репорте - это всегда report ID)
			for(int i=1; i<OutReportSz; i++) 									// заполняем буфер очередного OUT-репорта
			{   HexAnsiStr = SendStr.SubString(PacketsCounter*(OutReportSz-1)*2+(i-1)*2+1,2);	// получаем очередной кусочек строки с hex-числом
				HexStr = HexAnsiStr.c_str(); 									// преобразуем в char*
				HexToBin(HexStr,BinBuf,1);	 									// преобразуем строку char* в число
				OutReport[i] = BinBuf[0];	 									// вписываем очередные данные для передачи
			}
			if(HidD_SetOutputReport(DevHandle, OutReport, OutReportSz))	 		// если чтение не завершилось ошибкой
			{	ComposedStr = "";	  											// сначала строка пустая
				for(int i=1; i<OutReportSz; i++)   								// перебираем все полезные байты репорта
				{	ComposedStr = ComposedStr + " " + IntToHex(OutReport[i],2);	// преобразуем очередной байт в строковое представление hex
				}
				Form1->RichEdit1->Lines->Add("Отправлен пакет №" + IntToHex(PacketsCounter,2));	// сообщаем, что мы отправили пакет
				Form1->RichEdit1->Lines->Add("Sent data:" + ComposedStr);		//  выводим сформированную строку в лог
			}
			else
			{   Form1->RichEdit1->Lines->Add("Не удалось отправить пакет №" + IntToHex(PacketsCounter,2));	// сообщаем, что мы не отправили пакет
				OnError();														// запускаем функцию OnError
				return;		 													// завершаем операцию
			}
			PacketsCounter++;
		}
	}
	else
	{    Form1->RichEdit1->Lines->Add("Нет подключенных устройств");			// сообщаем, что нет открытых устройств
	}
}
//---------------------------------------------------------------------------
//--- читаем Feature-репорт из устройства -----------------------------------
void __fastcall TForm1::Button11Click(TObject *Sender)
{   UnicodeString	ComposedStr;												// здесь будем формировать строку вывода

	if(Running)																	// если устройство подключено
	{	if(HidD_GetFeature(DevHandle, FeatureReport, FeatureReportSz)) 	 		// если чтение не завершилось ошибкой
		{	ComposedStr = "";													// сначала строка пустая
			for(int i=1; i<FeatureReportSz; i++)								// перебираем все полезные байты репорта
			{	ComposedStr = ComposedStr + " " + IntToHex(FeatureReport[i],2);	// преобразуем очередной байт в строковое представление hex
			}
			Form1->RichEdit1->Lines->Add("Recieved data (feature):" + ComposedStr);	//  выводим сформированную строку в лог
		}
		else																	// если чтение завершилось ошибкой
		{	OnError();															// запускаем функцию OnError
		}
	}
	else
	{	Form1->RichEdit1->Lines->Add("Нет подключенных устройств");				// сообщаем, что нет открытых устройств
	}
}
//---------------------------------------------------------------------------
//--- отправляем Feature-репорт ---------------------------------------------
void __fastcall TForm1::Button8Click(TObject *Sender)
{	DWORD 			ByteCnt;   													// счётчик переданных байт
	AnsiString		SendStr;													// сама строка (в ANSI, чтобы по 1 байту на символ)
	AnsiString		HexAnsiStr;													// кусок с очередным Hex-числом
	int				SpaceIndex;													// индекс пробела в строке
	int				RemAfterDiv;												// отстаток от деления количества байт для передачи на количества полезных байт, посылаемых за 1 раз
	int				PacketsCounter;												// счётчик отправленных пакетов
	int				PacketsNum;													// общее количество пакетов, которые нужно отправить
	char 			*HexStr;													// здесь будем хранить строку с hex
	char			BinBuf[2];													// здесь окажется результат преобразования HexToBin
	UnicodeString	ComposedStr;												// здесь будем формировать строку вывода

	if(Running)
	{	SendStr = AnsiString(Form1->Edit1->Text);  								// получаем текст в виде Ansi-строки
		// удаляем все пробелы
		while((SpaceIndex = SendStr.Pos(" "))!=0)		 			  			// выполнять, пока пробелы есть (индекс не равен нулю)
		{	SendStr = SendStr.Delete(SpaceIndex,1);		 			  			// удаляем очередной пробел
		}
		RemAfterDiv = SendStr.Length()%(FeatureReportSz - 1); 	   				// узнаём, кратен ли размер посылки количеству полезных байт в одном OUT-репорте
		PacketsNum = SendStr.Length()/(2*(FeatureReportSz - 1));   				// узнаём количество пакетов для отправки (на 2 делим, поскольку 1 байт кодируется двумя символами)
		if(RemAfterDiv!=0)										   				// если данные не кратны 2*(размер репорта - 1)
		{	Form1->RichEdit1->Lines->Add("Количество данных для передачи не кратно количеству полезных данных репорта Feature.");
			Form1->RichEdit1->Lines->Add("Неполный репорт будет обрезан, последние данные не будут отправлены");	// сообщаем, что обрежем последний репорт
		}
		// организуем цикл для отправки
		PacketsCounter = 0;					 									// сбрасываем счётчик отправленных пакетов
		while(PacketsCounter < PacketsNum)	 									// пока не отправим все пакеты
		{	FeatureReport[0] = 0;			  									// report ID (нулевой байт в репорте - это всегда report ID)
			for(int i=1; i<FeatureReportSz; i++) 								// заполняем буфер очередного Feature-репорта
			{   HexAnsiStr = SendStr.SubString(PacketsCounter*(FeatureReportSz-1)*2+(i-1)*2+1,2);	// получаем очередной кусочек строки с hex-числом
				HexStr = HexAnsiStr.c_str(); 									// преобразуем в char*
				HexToBin(HexStr,BinBuf,1);	 									// преобразуем строку char* в число
				FeatureReport[i] = BinBuf[0];									// вписываем очередные данные для передачи
			}
			if(HidD_SetFeature(DevHandle, FeatureReport, FeatureReportSz)) 		// если чтение не завершилось ошибкой
			{	ComposedStr = "";	  											// сначала строка пустая
				for(int i=1; i<FeatureReportSz; i++)   	  						// перебираем все полезные байты репорта
				{	ComposedStr = ComposedStr + " " + IntToHex(FeatureReport[i],2);	// преобразуем очередной байт в строковое представление hex
				}
				Form1->RichEdit1->Lines->Add("Отправлен пакет (feature) №" + IntToHex(PacketsCounter,2));	// сообщаем, что мы отправили пакет
				Form1->RichEdit1->Lines->Add("Sent data (feature):" + ComposedStr);	//  выводим сформированную строку в лог
			}
			else
			{   Form1->RichEdit1->Lines->Add("Не удалось отправить пакет №" + IntToHex(PacketsCounter,2));	// сообщаем, что мы не отправили пакет
				OnError();														// запускаем функцию OnError
				return;		 													// завершаем операцию
			}
			PacketsCounter++;
		}
	}
	else
	{    Form1->RichEdit1->Lines->Add("Нет подключенных устройств");			// сообщаем, что нет открытых устройств
	}
}
//---------------------------------------------------------------------------

