Работа с BlueTooth в Delphi.
[+] Введение:
Все программы написаны на Delphi 6 и тестировались со стандартным стеком Bluetooth от Microsoft под Windows XP + SP2. При разработке использовал только API функции с JEDI. Описание функций будут даны в стиле Object Pascal. Microsoft в своей документации вводит два термина: Radio и Device. Radio - это локальный радиомодуль BlueTooth(USB-брелок, интегрированное решение, установленный на вашем компьютере). Device - это то устройство BlueTooth, с которым вы хотите обмениваться информацией. К сожаленью, документация Microsoft по BlueTooth API и работе с BlueTooth очень скудна и и из неё очень трудно понять как же всё-таки работает эта технология.
[+] Описание используемых функций:
Получение списка установленных радиомодулей BlueTooth:
BlueToothFindFirstRadio - начинает перечисление локальных радиомодулей BlueTooth.
function BlueToothFindFirstRadio(const pbtfrp: PBlueToothFindRadioParams; var phRadio: THandle): HBLUETOOTH_RADIO_FIND; stdcall;
pbtfrp - указатель на структуру BLUETOOTH_FIND_RADIO_PARAMS. Член dwSize этой структуры должен содержать размер структуры (устанавливается посредством SizeOf(BLUETOOTH_FIND_RADIO_PARAMS)).
phRadio - описатель найденного устройства.
В случае успешного выполнения функция вернёт корректный описатель в phRadio и корректный описатель в качестве результата. В случае ошибки будет возвращён 0. Для получения кода ошибки используйте функцию GetLastError.
2. BlueToothFindNextRadio - находит следующий установленный радиомодуль.
function BlueToothFindNextRadio(hFind: HBLUETOOTH_RADIO_FIND; var phRadio: THandle): Bool; stdcall;
hFind - описатель, который вернула функция BlueToothFindFirstRadio.
phRadio - сюда будет помещён описатель следующего найденного радиомодуля.
Функция вернёт TRUE, если устройство найдено. В phRadio корректный описатель на найденный радиомодуль. Функция вернёт FALSE, в случае отсутствия устройства. phRadio содержит некорректный описатель. Используйте GetLastError для получения кода ошибки.
3.BlueToothFindRadioClose - закрывает описатель перечисления радиомодулей BlueTooth.
function BlueToothFindRadioClose(hFind: HBLUETOOTH_RADIO_FIND): Bool;stdcall;
hFind - описатель, который вернула функция BlueToothFindFirstRadio.
Функция вернёт TRUE, если описатель успешно закрыт, и FALSE - в случае ошибки. Для получения кода ошибки используйте функцию GetLastError.
Процедура получения списка установленных радиомодулей BlueTooth:
procedure BlueToothRadio;
var
hRadio: THandle;
BFRP: BLUETOOTH_FIND_RADIO_PARAMS;
hFind: HBLUETOOTH_RADIO_FIND;
begin
// Инициализация структуры BLUETOOTH_FIND_RADIO_PARAMS
BFRP.dwSize := SizeOf(BFRP);
// Начинаем поиск
hFind := BluetoothFindFirstRadio(@BFRP, hRadio);
if (hFind <> 0) then
begin
repeat
// Что-то сделать с полученным описателем
// Закрыть описатель устройства
CloseHandle(hRadio);
// Находим следующее устройство
until (not BluetoothFindNextRadio(hFind, hRadio));
// Закрываем поиск
BluetoothFindRadioClose(hFind);
end;
end;
Получение информации о радиомодуле BlueTooth:
BlueToothGetRadioInfo - возвращает информацию о радиомодуле, который представлен описателем.
function BlueToothGetRadioInfo(hRadio: THandle; var pRadioInfo: BLUETOOTH_RADIO_INFO): DWord; stdcall;
hRadio - описатель локального радиомодуля, который получен функцией BlueToothFindRadioFirst или BlueToothFindRadioNext.
pRadioInfo - структура, в которую записывается информация об указанном радиомодуле. Член dwSize должен быть равен размеру структуры.
Функция вернёт ERROR_SUCCESS, если информация получена. В противном случае - код ошибки.
Структура BLUETOOTH_RADIO_INFO выглядит так:
BLUETOOTH_RADIO_INFO = record
dwSize : dword;
// Размер структуры в байтах
address : BLUETOOTH_ADDRESS;
// Адрес локального радиомодуля
szName : array [0..BLUETOOTH_MAX_NAME_SIZE - 1] of
widechar; // Имя радиомодуля
ulClassofDevice : ulong;
// Класс устройства
lmpSubversion : word;
// Устанавливается производителем
manufacturer : word;
// Код производителя (константы
// BTH_MFG_XXX). Для получения новых
// кодов обратитесь к сайту
// спецификации BlueTooth
end;
Процедура получения информации о радиомодуле BlueTooth:
procedure GetRadioInfo(hRadio: THandle);
var
RadioInfo: BLUETOOTH_RADIO_INFO;
begin
// Инициализация структуры BLUETOOTH_RADIO_INFO
FillChar(RadioInfo, 0, SizeOf(RadioInfo));
RadioInfo.dwSize := SizeOf(RadioInfo);
// Получаем информацию
if (BluetoothGetRadioInfo(hRadio, RadioInfo) = ERROR_SUCCESS)
then
begin
// Используем полученную информацию
end;
end;
Рабочий пример использования указанных функций можно
скачать здесь.
Получение списка устройств BlueTooth:
1. BlueToothFindFirstDevice -перечисление устройств BlueTooth.
function BlueToothFindFirstDevice(const pbtsp: BLUETOOTH_DEVICE_SEARCH_PARAMS; var pbtdi: BLUETOOTH_DEVICE_INFO): HBLUETOOTH_DEVICE_FIND;
pbtsp - указатель на структуру BLUETOOTH_FIND_RADIO_PARAMS. Член dwSize этой структуры должен содержать размер структуры (устанавливается посредством SizeOf(BLUETOOTH_FIND_RADIO_PARAMS)). Член hRadio должен содержать описатель локального радиомодуля, полученный вызовом функции BlueToothFindFirstRadio.
pbtdi - структура BLUETOOTH_DEVICE_INFO, в которую будет возвращена информация об устройстве BlueTooth.
В случае успешного выполнения функция вернёт корректный описатель в качестве результата. В случае ошибки будет возвращён 0. Для получения кода ошибки используйте функцию GetLastError.
Структура BLUETOOTH_DEVICE_SEARCH_PARAMS выглядит так:
BLUETOOTH_DEVICE_SEARCH_PARAMS = record
dwSize: DWord;
// Входной параметр. Должен быть равен размеру структуры
// (dwSize:=SizeOf(BLUETOOTH_DEVICE_SEARCH_PARAMS))
fReturnAuthenticated : Bool;
// Входной параметр. Функция будет возвращать устройства,
//прошедшие авторизацию
fReturnRemembered : Bool;
// Входной параметр. Функция будет возвращать устройства,
// запомненные ранее
fReturnUnknown : Bool;
// Входной параметр. Функция будет возвращать новые, либо
// неизвестные устройства
fReturnConnected : Bool;
// Входной параметр. Функция будет возвращать
// подключённые устройства
fIssueInquiry : Bool;
// Входной параметр. Заставляет функцию проверять
// устройства
cTimeoutMultiplier : UCHAR;
// Входной параметр. Time-out для проверки устройства.
hRadio :THandle;
// Handle радиомодуля, для которого проводится поиск
// устройств. Если равен 0, то проверяются все радиомодули
end;
2. BlueToothFindNextDevice -находит следующее устройство BlueTooth.
function BlueToothFindNextDevice(hFind: HBLUETOOTH_RADIO_FIND; var pbtdi: BLUETOOTH_DEVICE INFO): Bool; stdcall;
hFind - описатель, который вернула функция BlueToothFindFirstDevice.
pbtdi - структура BLUETOOTH_DEVICE_INFO, в которую будет помещена информация об устройстве.
Функция вернёт TRUE, если устройство найдено и FALSE в случае отсутствия устройства. phRadio содержит некорректный описатель. Используйте GetLastError для получения кода ошибки.
Структура BLUETOOTH_DEVICE_INFO выглядит так:
BLUETOOTH_DEVICE_INFO = record
dwSize: DWord;
// Входной параметр. Должен быть равен размеру
//структуры (dwSize:=SizeOf(BLUETOOTH_DEVICE_INFO))
Address: BLUETOOTH_ADDRESS;
// Адрес устройства BlueTooth
ulClassofDevice: ULONG;
// Класс устройства (константы с префиксом COD_XXX
// в JwaBlueToothAPI)
fConnected: Bool;
// Если TRUE, то устройство подключено/используется
fRemembered:Bool;
// Если TRUE, то устройство уже было ранее найдено
fAuthenticated :Bool;
// Если TRUE, то устройство прошло авторизацию
stLastSeen:SYSTEMTIME;
// Дата и время последнего обнаружения устройства
stLastUsed:SYSTEMTIME;
// Дата и время последнего использования устройства
szName : array [0..BLUETOOTH_MAX_NAME_SIZE - 1] of WideChar;
// Название устройства
end;
3. BlueToothFindDeviceClose - закрывает описатель перечисления устройств BlueTooth.
function BlueToothFindDeviceClose(hFind: HBLUETOOTH_DEVICE_FIND): Bool; stdcall;
hFind - описатель, который вернула функция BlueToothFindFirstDevice.
Функция вернёт TRUE, если описатель успешно закрыт и FALSE в случае ошибки. Для получения кода ошибки используйте GetLastError.
4. BlueToothGetDeviceInfo - возвращает информацию об указанном устройстве BlueTooth.
function BlueToothGetDeviceInfo(hRadio: THandle; var pbtdi: BLUETOOTH_DEVICE_INFO): DWord; stdcall;
hRadio - описатель локального радиомодуля BlueTooth.
pbtdi - &структура BLUETOOTH_DEVICE_INFO, в которую возвращается информация об устройстве. dwSize должен быть равен размеру структуры. Address - должен содержать адрес устройства, о котором хотим получить информацию.
Функция вернёт ERROR_SUCCESS, если выполнено успешно и информация занесена в структуру pbtdi. Остальные значения - код ошибки.
Процедура получения информации об устройствах BlueTooth:
procedure GetDevices(_hRadio: THandle);
var
DeviceInfo: PBLUETOOTH_DEVICE_INFO;
DeviceSearchParams: BLUETOOTH_DEVICE_SEARCH_PARAMS;
DeviceFind: HBLUETOOTH_DEVICE_FIND;
begin
// Инициализация структуры BLUETOOTH_DEVICE_SEARCH_PARAMS
with DeviceSearchParams do
begin
dwSize := SizeOf(BLUETOOTH_DEVICE_SEARCH_PARAMS);
fReturnRemembered := true; // Вернуть запомненные
hRadio := _hRadio
end;
// Инициализация структуры BLUETOOTH_DEVICE_INFO
FillChar(DeviceInfo, SizeOf(PBLUETOOTH_DEVICE_INFO), 0);
DeviceInfo.dwSize := SizeOf(PBLUETOOTH_DEVICE_INFO);
// Начинаем поиск
DeviceFind := BluetoothFindFirstDevice(DeviceSearchParams,
DeviceInfo);
if (DeviceFind <> 0) then
begin
repeat
// Что-то сделать с полученным данными
// Инициализация структуры BLUETOOTH_DEVICE_INFO
FillChar(DeviceInfo, SizeOf(PBLUETOOTH_DEVICE_INFO), 0);
DeviceInfo.dwSize := SizeOf(PBLUETOOTH_DEVICE_INFO);
// Находим следующее устройство
until (not BluetoothFindNextDevice(DeviceFind, DeviceInfo));
// Закрываем поиск
BluetoothFindDeviceClose(DeviceFind);
end;
end;
Набор классов для работы с BlueTooth API можно скачать здесь.
Несколько дополнительных функций:
1. BlueToothIsConnectable - определяет, возможно ли подключение к указанному радиомодулю
function BlueToothIsConnectable(const hRadio: THandle): Bool; stdcall;
hRadio - Handle радиомодуля, который мы проверяем. Если 0, то проверяются все радиомодули.
Функция вернёт TRUE, если указанный радиомодуль разрешает входящие подключения. Если hRadio=0, то вернёт TRUE, если хотя бы один радиомодуль разрешает входящие подключения. Если входящие подключения запрещены. то вернёт FALSE.
2. BlueToothIsDiscoverable - определяет, будет ли виден указанный радиомодуль другим при поиске. Если просматриваются все радиомодули, то вернёт TRUE, если хотя бы один разрешает обнаружение.
function BlueToothIsDiscoverable(const hRadio: THandle): Bool; stdcall;
hRadio - Handle радиомодуля, который мы проверяем. Если 0, то проверяются все радиомодули.
Функция вернёт TRUE, если указанный радиомодуль разрешает обнаружение. Если обнаружение запрещено, то вернёт FALSE.
3. BlueToothEnumerateInstalledServices - получает список GUID сервисов, предоставляемых устройством. Если параметр hRadio=0, то просматривает все радиомодули.
function BlueToothEnumerateInstalledServices(hRadio: THandle; pbtdi: _PBLUETOOTH_DEVICE_INFO; var pcServices: DWord; pGuidServices: PGUID): DWord; stdcall;
hRadio - Handle радиомодуля, который мы проверяем. Если 0. то проверяются все радиомодули.
pcServices - при вызове - количество записей в массиве pGuidServices. Возвращает в этом параметре реальное количество сервисов, предоставляемых устройством.
pbtdi - указатель на структуру PBLUETOOTH_DEVICE_INFO, в которой описанопроверяемое устройство. Необходимо заполнить поля dwSize и Address.
pGuidServices - указатель на массив TGUID, в который будут записаны GUID предоставляемых устройством сервисов. Если nil и pcServices=0, то в pcServices будет записано количество сервисов. Необходимо выделить для pGuidServices памятьразмером не менее pcServices*SizeOf(TGUID).
Функция вернёт ERROR_SUCCESS, если вызов успешен и количество сервисов в pcServices соответствует реальности. Функция вернёт ERROR_MORE_DATA, если вызов успешен, но выделенное количество памяти (pcServices при вызове) меньше. чем количество предоставляемых сервисов. В случае ошибки - другие коды ошибок Win32.
Пример кода получения списка сервисов:
// Получаем размер массива сервисов
ServiceCount := 0;
Services := nil;
hRadio := ASelected.Parent.ImageIndex;
Info := ASelected.Data;
BluetoothEnumerateInstalledServices(hRadio, Info, ServiceCount, nil);
// Выделяем память.
SetLength(Services, ServiceCount);
// Получаем список сервисов;
BluetoothEnumerateInstalledServices(hRadio, Info, ServiceCount, PGUID(Services)).
Здесь мы сначала вызываем функцию с pcServices=0 и pGuidServices=nil для того, чтобы получить количество сервисов, предоставляемых устройством. Потом выделяем память (SetLength( )) и только за тем вызываем функцию с реальными параметрами и получаем список сервисов. В файле JwaBlueToothAPI.pas параметр pbtdi имеет тип PBLUETOOTH_DEVICE_INFO, который раскрывается в BLUETOOTH_DEVICE_INFO. Замечу, что это не указатель. Это не верно, так как в исходном коде функция требует именно указатель, поэтому вводим тип:
type _PBLUETOOTH_DEVICE_INFO=^PBLUETOOTH_DEVICE_INFO; вводим тип:
Поэтому лучше использовать файл из примера, а не из библиотеки. Иначе получите нарушение доступа к памяти.
4. BlueToothEnableInfomingConnection - функция запрещает/разрешает подключения к локальному радиомодулю BlueTooth
function BlueToothEnableInfomingConnection(hRadio: THandle; fEnable: Bool): Bool; stdcall;
hRadio - Handle радиомодуля, статус которого мы хотим изменить. Если 0, то меняем у всех.
fEnable - если TRUE - разрешаем подключение. FALSE - запрещаем.
Функция возвращает TRUE, если вызов успешен и статус изменён. FALSE - в противном случае.
5. BlueToothEnableDiscovery - функция запрещает/разрешает обнаружение локального радиомодуля BlueTooth.
function BlueToothEnableDiscovery(hRadio: THandle; fEnable: Bool): Bool; stdcall;
hRadio - Handle радиомодуля, статус которого мы хотим ихменить. Если 0, то меняем у всех.
fEnable - если TRUE - разрешаем обнаружение, FALSE - запрещаем.
Функция вернёт TRUE, если вызов успешен и статус изменён. FALSE в противном случае.
6. BlueToothDisplayDeviceProperty - функция выводит стандартное окно свойств устройства BlueTooth.
function BlueToothDisplayDeviceProperty(hwndParent: HWND; var pbtdi: PBLUETOOTH_DEVICE_INFO): Bool; stdcall;
hwndParent - Handle родительского окна, которому будет принадлежать диалог свойств. Может быть 0, тогда родительским выбирается окно Desktop.
pbtdi - указатель на структуру BLUETOOTH_DEVICE_INFO, в которой содержится адрес требуемого устройства.
Функция возвращает TRUE, если вызов успешен. Возвращает FALSE - в противном случае(код ошибки можно узнать вызовом функции GetLastError).
7. BlueToothSelectDevices - функция запрещает/разрешает обнаружение локального радиомодуля BlueTooth.
function BlueToothSelectDevices(pbtsdp: PBLUETOOTH_SELECT_DEVICE_PARAMS): Bool; stdcall;
Если функция вернула TRUE, то пользователь выбрал устройства. Pbtsdp^.pDevices будет указывать на корректные данные. После вызова необходимо проверить флаги fAutheniticated и fRemembered, чтобы удостовериться в корректности данных. Для освобождения памяти используйте функцию BlueToothSelectDevicesFree, только если функция вернёт TRUE.
Возможные ошибки:
ERROR_CANCELLED | Пользователь отменил выбор устройства |
ERROR_INVALID_PARAMETER | Параметр pbtsdp равен nil |
ERROR_REVISION_MISMATCH | Структура, переданная в pbtsdp неизвестного или неверного размера |
Другие ошибки WIN32 |
Структура BLUETOOTH_SELECT_DEVICE_PARAMS выглядит так:
BLUETOOTH_SELECT_DEVICE_PARAMS = record
dwSize : DWord;
// Должен быть равен размеру структуры
//(dwSize:=SizeOf(BLUETOOTH_RADIO_INFO))
cNumOfClasses : ULONG;
// Входной параметр. Количество записей в
// массиве prgClassOfDevice. Если 0, то
// ищутся все устройства
prgClassOfDevices : PBlueToothCodPairs;
// Входной параметр. Массив COD (классов
// устройств), которые необходимо искать
pszInfo : LPWSTR;
// Входной параметр. Если не nil, то задаёт
// текст заголовка окна выбора устройства
hwndParent : HWND;
// Входной параметр. Handle родительского
// окна для диалога выбора устройства. Если
// 0, то родителем будет Desktop
fForceAuthentication : Bool;
// Входной параметр. Если TRUE, то требует
// принудительной авторизации устройств
fShowAuthenticated : Bool;
// Входной параметр. Если TRUE, то
// авторизованные устройства будут
// доступны для выбора
fShowRemembered : Bool;
// Входной параметр. Если TRUE, то
// запомненные устройства будут доступны
// для выбора
fShowUnknown : Bool;
// Входной параметр. Если TRUE, то
// неизвестные устройства будут доступны
// для выбора
fAddNewDeviceWizard : Bool;
// Входной параметр. Если TRUE, то
// запускает мастер добавления нового
// устройства
fSkipServicesPage : Bool;
// Входной параметр. Если TRUE, то
// пропускает страницу Сервисы в мастере
pfnDeviceCallback : PFN_DEVICE_CALLBACK;
// Входной параметр. Если не nil, то
// является указателем на функцию
// обратного вызова, которая вызывается
// для каждого найденного устройства. Если
// функция вернёт TRUE, то устройства
// добавляются в список, если нет, то
// устройство игнорируется.
pvParam : Pointer;
// Входной параметр. Его значение будет
// передано функции pfnDeviceCallback в
// качестве параметра pvParam
cNumDevices : DWord;
// Как входной параметр - количество
// устройств, которое требуется вернуть.
// Если 0, то нет ограничений. Как
// выходной параметр - количество
// возвращённых устройств(выбранных).
pDevices : __PBLUETOOTH_DEVICE_INFO;
// Выходной параметр. Указатель на
// массив структур
// BLUETOOTH_DEVICE_INFO. Для его
// освобождения используйте функцию
// BlueToothSelectDevicesFree
end;
В оригинале этот параметр объявлен как PBLUETOOTH_DEVICE_INFO. BlueToothSelectDeviceFree - функция должна вызываться только если вызов BlueToothSelectDevices был успешен. Эта функция освобождает память и ресурсы, задействованные функцией BlueToothSelectDevices в структуре BLUETOOTH_SELECT_DEVICE_PARAMS.
8. BlueToothSetServiceState - описатель радиомодуля
function BlueToothSetServiceState(hRadio: THandle; var pbtdi: PBLUETOOTH_DEVICE_INFO; const pGuidService: TGUID; dwServiceFlags: DWord): DWord; stdcall;
hRadio - описатель радиомодуля
pbtdi - указатель на структуру BLUETOOTH_DEVICE_INFO
pGuidService - GUID сервиса, который необходимо включить/выключить
dwServiceFlags - флаги управления сервисом:
BLUETOOTH_SERVICE_DISABLE | выключает сервис |
BLUETOOTH_SERVICE_ENABLE | включает сервис |
Функция возвращает ERROR_SUCCESS, если вызов прошёл успешно. Если вызов не удался вернёт один из следующих кодов:
ERROR_INVALID_PARAMETER | Неверные флаги в dwServiceFlags |
RROR_SERVICE_DOES_NOT_EXIST | Указанный сервис не поддерживается |
Другие ошибки WIN32 |
В оригинале функция выглядит иначе, но это не верно. В документации Microsoft указано, что параметр pbtdi должен передаваться как указатель(что подразумевает запись PBLUETOOTH_DEVICE_INFO), но этот тип ошибочен. Он не является указателем.
9. BlueToothRemoveDevice - функция удаляет авторизацию между компьютером и устройством BlueTooth, а также очищает кэш-записи об этом устройстве.
function BlueToothRemoveDevice(var pAddress: BLUETOOTH_ADDRESS): DWord; stdcall;
hAddress - адрес устройства, которое удаляется.
Возвращаемые значения:
ERROR_SUCCESS | Устройство удалено |
ERROR_NOT_FOUND | Устройство не найдено |
10. BLueToothUpdateDeviceRecord - функция обновляет данные об устройстве в кэше.
function BlueToothUpdateDeviceRecord(var pbtdi: BLUETOOTH_DEVICE_INFO): DWord; stdcall;
pbtdi - указатель на структуру BLUETOOTH_DEVICE_INFO. В ней должны быть заполнены поля: dwSize(размер структуры), Address(адрес устройства), szName(новое имя устройства).
Возвращаемые значения:
ERROR_SUCCESS | Функция выполнена успешно |
ERROR_INVALID_PARAMETER | Указатель pbtdi=nil. (Для варианта Delphi нереально, так как указатель мы получаем из структуры, передавая её как var-параметр) |
ERROR_REVISION_MISMATCH | Размер структуры в dwSize не правильный |
Другие ошибки WIN32 |
Функции авторизации:
1. BlueToothAuthenticateDevice - отправляет запрос на авторизацию удалённому устройству BlueTooth. Есть два режима авторизации: "Wizard mode" и "Blind mode".
Wizard mode - запускается, когда параметр pszPasskey=nil. В этом случае открывается окно Мастера подключений. У пользователя будет запрошен пароль, который будет отправлен в запросе на авторизацию удалённому устройству. Пользователь будет оповещён системой об успешном или не успешном выполнении авторизации и получит возможность попытаться авторизовать устройства ещё раз.
Blind mode - вызывается. когда pszPasskey<>nil. В этом случае вам необходимо программно запросить код авторизации (pszPasskey) и уведомить пользователя о результате.
function BlueToothAuthenticateDevice(hwndParent: HWND; hRadio: THandle; pbtdi: BLUETOOTH_DEVICE_INFO; pszPasskey: PWideChar; ulPasskeyLength: ULong): DWord; stdcall;
hwndParent - Handle родительского окна. Если 0, то родительским окном станет окно Desktop.
hRadio - Handle локального радиомодуля. Если 0, то авторизация будет проведена на всех радиомодулях. Если хотя бы один пройдёт авторизацию, функция выполнится успешно.
pbtdi - информация об устройстве на котором необходимо авторизоваться
pszPasskey - PIN для авторизации. Если nil, то вызывается мастер авторизации. PszPasskey - не NULL-терминированная строка!
ulPasskeyLength - Длина строки в байтах. Должна быть меньше либо равна BLUETOOTH_MAX_PASSKEY_SIZE*SizeOf(WCHAR).
Возвращаемые значения:
ERROR_SUCCESS | Функция выполнена успешно |
ERROR_CANCELLED | Пользователь отменил процесс авторизации |
ERROR_INVALID_PARAMETER | Структура pbtdi не верна |
ERROR_NO_MORE_ITEMS | Устройство в pbtdi уже авторизовано |
Другие ошибки WIN32 |
Для Blind mode соответствие кодов ошибки BlueTooth кодам ошибок WIN32 приведено в таблице:
BlueTooth | WIN32 |
BTH_ERROR_SUCCESS | ERROR_SUCCESS |
BTH_ERROR_NO_CONNECTION | ERROR_DEVICE_NOT_CONNECTED |
BTH_ERROR_PAGE_TIMEOUT | WAIT_TIMEOUT |
BTH_ERROR_HARDWARE_FAILURE | ERROR_GEN_FAILURE |
BTH_ERROR_AUTHENTICATION_FAILURE | ERROR_NOT_AUTHENTICATED |
BTH_ERROR_MEMORY_FULL | ERROR_NOT_ENOUGH_MEMORY |
BTH_ERROR_CONNECTION_TIMEOUT | WAIT_TIMEOUT |
BTH_ERROR_LMP_RESPONSE_TIMEOUT | WAIT_TIMEOUT |
BTH_ERROR_MAX_NUMBER_OF_CONNECTIONS | ERROR_REQ_NOT_ACCEPT |
BTH_ERROR_PAIRING_NOT_ALLOWED | ERROR_ACCESS_DENIED |
BTH_ERROR_UNSPECIFIED_ERROR | ERROR_NOT_READY |
BTH_ERROR_LOCAL_HOST_TERMINATED_CONNECTION | ERROR_VC_DISCONNECTED |
2. BlueToothAuthenticateMultipleDevices - позволяет авторизоваться сразу на нескольких устройствах при помощи одной копии Мастера авторизации.
function BlueToothAuthenticateMultipleDevices(hwndParent: HWND; hRadio: THandle; cDevices: DWord; rgpbtdi: PBLUETOOTH_DEVICE_INFO): DWord; stdcall;
hwndParent - Handle родительского окна. Если 0, то родительским окном станет окно Desktop.
hRadio - Handle локального радиомодуля. Если 0, то авторизация будет проведена на всех радиомодулях. Если хотя бы один пройдёт авторизацию, функция выполнится успешно.
cDevices - количество элементов в массиве rgpbtdi
rgpbtdi - массив структур BLUETOOTH_DEVICE_INFO, в котором представлены устройства для авторизации.
Возвращаемые значения:
ERROR_SUCCESS | Функция выполнена успешно. Проверьте флаг fAuthenticated у каждого устройства, чтобы знать, какие прошли авторизацию |
ERROR_CANCELLED | Пользователь отменил процесс авторизации. проверьте флаг fAuthenticated у каждого устройства, чтобы знать, какие прошли авторизацию |
ERROR_INVALID_PARAMETER | Один или несколько элементов массива rgpbtdi не верны |
ERROR_NO_MORE_ITEMS | Все устройства в массиве уже авторизованы |
Другие ошибки WIN32 |
В оригинале функция выглядит иначе. Но в документации Microsoft сказано, что параметр rgpbtdi передаётся как указатель (что подразумевает запись PBLUETOOTH_DEVICE_INFO), но этот тип ошибочен. Он не является указателем.
3. BlueToothRegisterForAuthentication - регистрирует функцию обратного вызова, которая будет вызываться на запрос устройства об авторизации. Если несколько приложений зарегистрировало такую функцию, то будет вызвана функция в последнем приложении.
function BlueToothRegisterForAuthentication(var pbtdi: PBLUETOOTH_DEVICE_INFO; var phRegHandle: HBLUETOOTH_AUTHENTICATION_REGISTRATION; pfnCallback: PFN_AUTHENTICATION_CALLBACK; pvParam: Pointer): DWord; stdcall;
pbtdi - Указатель на BLUETOOTH_DEVICE_INFO. Используется адрес устройства, для которого регистрируется функция. Обратите внимание на параметр. В оригинале он опять передаётся не как указатель.
phRegHandle - указатель, куда будет возвращён Handle регистрации, которой потом используется в BlueToothUnregisterAuthentication.
pfnCallback - Функция обратного вызова.
pvParam - опциональный параметр, который без изменения передаётся в функцию обратного вызова.
Возвращаемые значения:
ERROR_SUCCESS | Функция выполнена успешно |
ERROR_OUTOFMEMORY | Недостаточно памяти |
Другие ошибки WIN32 |
4. BlueToothUnregisterAuthentication - удаляет функцию обратного вызова, зарегистрированную функцией BlueToothRegisterForAuthentication и закрывает Handle.
functionBlueToothUnregisterAuthentication (hRegHandle: HBLUETOOTH_AUTHENTICATION_REGISTRATION): Bool; stdcall;
hRegHandle - Handle регистрации, полученный функцией BlueToothRegisterForAuthentication.
Функция вернёт TRUE, если вызов успешен и FALSE - в случае неудачи. Используйте GetLastError для получения дополнительной информации.
5. BlueToothSendAuthenticationResponse - функция должна вызываться из функции обратного вызова при запросе авторизации удалённым устройством для передачи PIN.
functionBlueToothSendAuthenticationResponse (hRadio: THandle; pbtdi: PBLUETOOTH_DEVICE_INFO; pszPasskey: LPWSTR): DWord; stdcall;
hRadio - Handle радиомодуля, для которого проводим авторизацию. Если 0, то пытаемся авторизовать на всех устройствах.
pbtdi - Указатель на BLUETOOTH_DEVICE_INFO с данными об устройстве, от которого поступил запрос на авторизацию. Может быть тот же указатель, который передан в функцию обратного вызова.
pszPasskey - Указатель на UNICODE строку. в которой содержится ключ авторизации(PIN).
Возвращаемые значения:
ERROR_SUCCESS | Функция выполнена успешно |
ERROR_CANCELLED | Устройство отвергло авторизационный код(PIN) или проблемы со связью |
E_FAIL | Устройство вернуло ошибку авторизации |
Другие ошибки WIN32 |
6. PFN_AUTHENTICATION_CALLBACK - функция обратной связи
PFN_AUTHENTICATION_CALLBACK = function(pvParam: Pointer; pDevice: PBLUETOOTH_DEVICE_INFO): Bool; stdcall;
pvParam - указатель на параметр,который мы передали в BlueToothRegisterForAuthentication.
pDevice - указатель на BLUETOOTH_DEVICE_INFO с данными об устройстве, от которого поступил запрос на авторизацию.
[+] Создание полноценного BlueTooth-приложения:
Создайте в Delphi новый проект и сохраните его под именем BTWork, а модуль - под именем Main. Главную форму назовите fmMain с заголовком BTWork. Теперь нам понадобится файлы JwaBlueToothAPI.pas, JwaBTHDef.pas и JwaBThSdpDef.pas(библиотека BTClasses). Найдите в них строку "uses JwaWindows" и замените её на "Windows". Далее удалите из них строки:
{$WEAKPACKAGEUNIT}
{$HPPEMIT"}
{$HPPEMIT'#include "bluetoothapis.h"'}
{$HPPEMIT"}
{$I jediapilib.inc}
И в файле JwaBlutoothAPI удалите всё, что находится между{$IFDEF DYNAMIC_LINK} и {$ELSE} вместе с этими DEF. И в конце файла удалите{$ENDIF}.
Далее, в JwaBlueToothAPI.pas после implementation:
uses JwaWinDLLNames;
Напишите const btapi='irprops.cpl';
Эти действия нужны для того, чтобы уменьшить архив примера. Теперь добавьте полученные модули в проект.
Оформление главной формы:
На главную форму поместим компонент TPanel и установите следующие свойства:
Свойство | Значение |
Align | alTop |
Caption | |
Name | Panel |
Далее поместите компонент TTreeView и установите свойства как в таблице:
Свойство | Значение |
Align | alLeft |
Cursor | crHandPoint |
HideSelection | False |
HotTrack | True |
Name | TreeView |
ReadOnly | True |
Правее TTreeView поместим TSplitter и установим следующие его свойства:
Свойство | Значение |
Name | Splitter |
Width | 5 |
Компонент TListView поместите ещё правее TSplitter и установите его свойства такими:
Свойство | Значение |
Align | alClient |
ColumnClick | False |
Cursor | crHandPoint |
GridLines | True |
HideSelection | False |
HotTrack | True |
Name | ListView |
ReadOnly | True |
RowSelect | True |
ShowWorkAreas | True |
ViewStyle | vsReport |
На TPanel поместим кнопку TButton:
Свойство | Значение |
Caption | Refresh |
Name | btRefresh |
При старте программы желательно, чтобы сразу заполнялся TreeView. В нём будут показаны модули BlueTooth и устройства, которые к ним подключены. Для этого в обработчике OnCreate формы fmMain напишем такой код:
procedure TfmMain.FormCreate(Sender: TObject);
begin
btRefresh.Click;
end;
А в обработчике OnClick кнопки btRefresh напишем следующее:
procedure TfmMain.btRefreshClick(Sender: TObject);
var
RootNode: TTreeNode;
hFind: HBLUETOOTH_RADIO_FIND;
hDevFind: HBLUETOOTH_DEVICE_FIND;
FindParams: BLUETOOTH_FIND_RADIO_PARAMS;
SearchParams: BLUETOOTH_DEVICE_SEARCH_PARAMS;
SearchParamsSize: dword;
DevInfo: ^PBLUETOOTH_DEVICE_INFO;
DevInfoSize: dword;
hRadio: THandle;
RadioInfo: PBLUETOOTH_RADIO_INFO;
RadioInfoSize: dword;
RadioNode: TTreeNode;
Loop: integer;
DevNode: TTreeNode;
begin
with TreeView.Items do
begin
BeginUpdate;
// Очищаем дерево
for Loop := 0 to Count - 1 do
begin
if TreeView.Items[Loop].ImageIndex > 0 then
CloseHandle(TreeView.Items[Loop].ImageIndex);
if Assigned(TreeView.Items[Loop].Data) then
Dispose(TreeView.Items[Loop].Data);
end;
Clear;
// Корневая ветвь в дереве
RootNode := Add(nil, 'Bluetooth Radios');
with RootNode do
begin
Data := nil;
ImageIndex := -1;
end;
// Начинаем поиск локальных модулей Bluetooth
FindParams.dwSize := SizeOf(BLUETOOTH_FIND_RADIO_PARAMS);
hFind := BluetoothFindFirstRadio(@FindParams, hRadio);
if hFind <> 0 then
begin
repeat
// Получить информацию о радиомодуле
New(RadioInfo);
RadioInfoSize := SizeOf(BLUETOOTH_RADIO_INFO);
FillChar(RadioInfo^, RadioInfoSize, 0);
RadioInfo^.dwSize := RadioInfoSize;
// Ошибки не обрабатываем!!!
BluetoothGetRadioInfo(hRadio, RadioInfo^);
// Добавляем радио в дерево
RadioNode := AddChild(RootNode, string(RadioInfo^.szName) + ' [' +
BTAdrToStr(RadioInfo^.address) + ']');
with RadioNode do
begin
// Так как мы сохраняем Handle, то не закрываем его!
ImageIndex := hRadio;
Data := RadioInfo;
end;
// Начинаем поиск устройств для найденного радиомодуля.
SearchParamsSize := SizeOf(BLUETOOTH_DEVICE_SEARCH_PARAMS);
FillChar(SearchParams, SearchParamsSize, 0);
SearchParams.dwSize := SearchParamsSize;
SearchParams.fReturnRemembered := True;
SearchParams.hRadio := hRadio;
New(DevInfo);
DevInfoSize := SizeOf(BLUETOOTH_DEVICE_INFO);
FillChar(DevInfo^, DevInfoSize, 0);
DevInfo^.dwSize := DevInfoSize;
// Ищем первое
hDevFind := BluetoothFindFirstDevice(SearchParams, DevInfo^);
if hDevFind <> 0 then
begin
repeat
// Добавляем в дерево
DevNode := AddChild(RadioNode, string(DevInfo^.szName) + ' [' +
BTAdrToStr(DevInfo^.Address) + ']');
with DevNode do
begin
Data := DevInfo;
ImageIndex := -2;
end;
// Ищем следующее устройство
New(DevInfo);
DevInfoSize := SizeOf(BLUETOOTH_DEVICE_INFO);
FillChar(DevInfo^, DevInfoSize, 0);
DevInfo^.dwSize := DevInfoSize;
until not BluetoothFindNextDevice(hDevFind, DevInfo^);
// Поиск устройств закончен
BluetoothFindDeviceClose(hDevFind);
end;
// Находим следующее радио
until not BluetoothFindNextRadio(hFind, hRadio);
// Поиск радиомодулей закончен
BluetoothFindRadioClose(hFind);
end;
EndUpdate;
end;
with TreeView do
begin
Selected := RootNode;
Items[0].Expand(True);
end;
end;
В uses нашего модуля, который относится к главной форме, допишем: implementation (Уже написано), uses (Дописать) JwaBluetoothAPIs, Windows, SysUtils, Dialogs. Ниже добавим функцию: (Преобразует адрес из внутреннего формата (dword) в строку, принятую для представления адресов устройств Bluetooth).
function BTAdrToStr(const Adr: BLUETOOTH_ADDRESS): string;
var
Loop: byte;
begin
Result := IntToHex(Adr.rgBytes[0], 2);
for Loop := 1 to 5 do
Result := IntToHex(Adr.rgBytes[Loop], 2) + ‘:’ + Result;
end;
Далее напишите обработчик события OnChange для TreeView:
procedure TfmMain.TreeViewChange(Sender: TObject; Node: TTreeNode);
var ASelected: TTreeNode;
procedure ShowRadios;
var Info: PBLUETOOTH_RADIO_INFO;
CurNode: TTreeNode;
begin
// Строим столбцы
with ListView.Columns do
begin
BeginUpdate;
with Add do Caption := 'Address';
with Add do Caption := 'Name';
with Add do Caption := 'Class Of Device';
with Add do Caption := 'Manufacturer';
with Add do Caption := 'Subversion';
with Add do Caption := 'Connectable';
with Add do Caption := 'Discoverable';
EndUpdate;
end;
// Заполняем список
with ListView.Items do
begin
BeginUpdate;
CurNode := ASelected.GetFirstChild;
while Assigned(CurNode) do
begin
Info := PBLUETOOTH_RADIO_INFO(CurNode.Data);
// Перечитать информацию о радиомодуле
BluetoothGetRadioInfo(CurNode.ImageIndex, Info^);
with Add do
begin
Data := Pointer(CurNode.ImageIndex);
Caption := BTAdrToStr(Info.address);
with SubItems do
begin
Add(string(Info.szName));
Add(IntToStr(Info.ulClassofDevice));
Add(IntToStr(Info.manufacturer));
Add(IntToStr(Info.lmpSubversion));
Add(BoolToStr(BluetoothIsConnectable(CurNode.ImageIndex), True));
Add(BoolToStr(BluetoothIsDiscoverable(CurNode.ImageIndex), True));
end;
end;
CurNode := ASelected.GetNextChild(CurNode);
end;
EndUpdate;
end;
end;
procedure ShowDevices;
var
Info: ^PBLUETOOTH_DEVICE_INFO;
CurNode: TTreeNode;
begin
// Строим столбцы
with ListView.Columns do
begin
BeginUpdate;
with Add do Caption := 'Address';
with Add do Caption := 'Name';
with Add do Caption := 'Class Of Device';
with Add do Caption := 'Connected';
with Add do Caption := 'Remembered';
with Add do Caption := 'Authenticated';
with Add do Caption := 'Last Seen';
with Add do Caption := 'Last Used';
EndUpdate;
end;
//Заполняем список
with ListView.Items do
begin
BeginUpdate;
CurNode := ASelected.GetFirstChild;
while Assigned(CurNode) do
begin
Info := CurNode.Data;
// Перечитываем информацию об устройстве
// Так как передаем указатель, то она автоматом
// обновится и в том месте, где мы ее сохраняли
BluetoothGetDeviceInfo(ASelected.ImageIndex, Info^);
with Add do
begin
Data := Info;
Caption := BTAdrToStr(Info^.Address);
with SubItems do
begin
Add(string(Info^.szName));
Add(IntToStr(Info^.ulClassofDevice));
Add(BoolToStr(Info^.fConnected, True));
Add(BoolToStr(Info^.fRemembered, True));
Add(BoolToStr(Info^.fAuthenticated, True));
try
// stLastSeen может быть 0 и тогда здесь ошибка будет
Add(DateTimeToStr(SystemTimeToDateTime(Info^.stLastSeen)));
except
Add(‘’);
end;
try
// stLastUsed может быть 0 и тогда здесь ошибка будет
Add(DateTimeToStr(SystemTimeToDateTime(Info^.stLastUsed)));
except
Add(‘’);
end;
end;
end;
CurNode := ASelected.GetNextChild(CurNode);
end;
EndUpdate;
end;
end;
procedure ShowServices;
var
Info: __PBLUETOOTH_DEVICE_INFO;
ServiceCount: dword;
Services: array of TGUID;
hRadio: THandle;
Loop: integer;
begin
// Строим столбцы
with ListView.Columns do
begin
BeginUpdate;
with Add do Caption := 'GUID';
EndUpdate;
end;
// Заполняем список
with ListView.Items do
begin
BeginUpdate;
// Получаем размер массива сервисов
ServiceCount := 0;
Services := nil;
hRadio := ASelected.Parent.ImageIndex;
Info := ASelected.Data;
BluetoothEnumerateInstalledServices (hRadio, Info, ServiceCount, nil);
// Выделяем память.
SetLength(Services, ServiceCount);
// Получаем список сервисов
BluetoothEnumerateInstalledServices (hRadio, Info, ServiceCount,
PGUID(Services));
// Рисуем их
for Loop := 0 to ServiceCount - 1 do
with Add do
Caption := GUIDToString(Services[Loop]);
// Очищаем память
Services := nil;
EndUpdate;
end;
end;
begin
ASelected := TreeView.Selected;
// Очищаем ListView
with ListView do
begin
with Columns do
begin
BeginUpdate;
Clear;
EndUpdate;
end;
with Items do
begin
BeginUpdate;
Clear;
EndUpdate;
end;
end;
// Заполняем информацией
if Assigned(ASelected) then
case ASelected.ImageIndex of
-2: ShowServices;
-1: ShowRadios;
else
if ASelected.ImageIndex > 0 then ShowDevices;
end;
end;
Добавьте функции запрета/разрешения обнаружения радиомодуля и запрета/разрешения подключения к нему - BlueToothEnableIncomingConnections и BlueToothEnableDiscoverable. Поместите на фору компонент TActionList и измените его свойства на:
Свойство | Значение |
Name | ActionList |
Теперь два раза щёлкните по ActionList и в появившемся окне редактора свойств добавьте две TAction со следующими свойствами
Свойство | Значение |
Caption | Connectable |
Name | acConnectable |
Свойство | Значение |
Caption | Discoverable |
Name | acDiscoverable |
На панель Panel добавьте две TButton и установите свойства:
Свойство | Значение |
Action | acConnectable |
Name | btConnectable |
Свойство | Значение |
Action | acDiscoverable |
Name | btDiscoverable |
Напишите обработчик события OnUpdate для acConnectable:
procedure TfmMain.acConnectableUpdate(Sender: TObject);
var SelectedItem: TListItem;
SelectedNode: TTreeNode;
begin
SelectedNode := TreeView.Selected;
SelectedItem := ListView.Selected;
with TAction(Sender) do
begin
Enabled := Assigned(SelectedNode) and Assigned(SelectedItem)
and (SelectedNode.ImageIndex = -1);
if Enabled then
if StrToBool(SelectedItem.SubItems[4])
then Caption := 'Not conn.'
else Caption := 'Connectable';
end;
end;
Тоже самое напишите для обработчика события OnUpdate для acDiscoverable:
procedure TfmMain.acDiscoverableUpdate(Sender: TObject);
var SelectedItem: TListItem;
SelectedNode: TTreeNode;
begin
SelectedNode := TreeView.Selected;
SelectedItem := ListView.Selected;
with TAction(Sender) do
begin
Enabled := Assigned(SelectedNode) and Assigned(SelectedItem)
and (SelectedNode.ImageIndex = -1);
if Enabled then
if StrToBool(SelectedItem.SubItems[5])
then Caption := 'Not disc.'
else Caption := 'Discoverable';
end;
end;
Теперь обработчик события OnExecute для acConnectable:
procedure TfmMain.acConnectableExecute(Sender: TObject);
var SelectedItem: TListItem;
begin
SelectedItem := ListView.Selected;
if Assigned(SelectedItem) then
if not
BluetoothEnableIncomingConnections(Integer(SelectedItem.Data),
TAction(Sender).Caption = 'Not conn.')
then MessageDlg('Unable to change Radio state', mtError,[mbOK],0)
else TreeViewChange(TreeView, TreeView.Selected);
end;
Такой же обработчик события OnExecute нужен и для acDiscoverable:
procedure TfmMain.acConnectableExecute(Sender: TObject);
var SelectedItem: TListItem;
begin
SelectedItem := ListView.Selected;
if Assigned(SelectedItem) then
if not BluetoothEnableDiscovery(Integer(SelectedItem.Data),
TAction(Sender).Caption = 'Not disc.')
then MessageDlg('Unable to change Radio state', mtError,[mbOK], 0)
else TreeViewChange(TreeView, TreeView.Selected);
end;
Если Windows сам использует радиомодуль, то он не даст поменять статус, хотя и функция выполнится без ошибок! Для вывода системного окна свойств устройства BlueTooth добавьте к ActionList ещё один ещё один TAction с такими свойствами:
Свойство | Значение |
Caption | Property |
Name | acProperty |
Добавьте на Panel кнопку TButton с такими свойствами:
Свойство | Значение |
Action | acProperty |
Name | btProperty |
Теперь напишите такой обработчик событий OnUpdate у acProperty:
procedure TfmMain.acPropertyUpdate(Sender: TObject);
var SelectedNode: TTreeNode;
SelectedItem: TListItem;
begin
SelectedNode := TreeView.Selected;
SelectedItem := ListView.Selected;
TAction(Sender).Enabled := Assigned(SelectedNode) and Assigned(SelectedItem) and
(SelectedNode.ImageIndex > 0);
end;
И обработчик OnExecute для неё же:
procedure TfmMain.acPropertyExecute(Sender: TObject);
varInfo: BLUETOOTH_DEVICE_INFO;
begin
Info := BLUETOOTH_DEVICE_INFO(ListView.Selected.Data^);
BluВ исходном виде в файле JwaBlueToothAPI.pas функция BlueToothDisplayDeviceProperties объявлена не верно. Второй параметр должен быть указателем, а там он передаётся как структура. Я исправил функцию так, чтобы он передавался как var-параметр(по ссылке). Используйте модуль JwaBlueToothAPI.pas из этого примера, чтобы не возникало ошибок доступа к памяти. Ни в этой процедуре, ни ранее, ни далее я не привожу проверку ошибок, чтобы не загромождать код лишними подробностями. В реальном приложении необходимо проверять возвращаемые функциями значения и указатели.
Для вызова диалога выбора устройств добавьте в проект на Panel ещё одну кнопку TButton и установите её свойства такими:
Свойство | Значение |
Caption | Select |
Name | btSelect |
Напишите обработчик события OnClick у этой кнопки:
procedure TfmMain.btSelectClick(Sender: TObject);
varASelParams: BLUETOOTH_SELECT_DEVICE_PARAMS;
ASelParamsSize: dword;
begin
ASelParamsSize := SizeOf(BLUETOOTH_SELECT_DEVICE_PARAMS);
FillChar(ASelParams, ASelParamsSize, 0);
with ASelParams do
begin
dwSize := ASelParamsSize;
hwndParent := Handle;
fShowRemembered := True;
fAddNewDeviceWizard := True;
end;
BluetoothSelectDevices(@ASelParams);
BluetoothSelectDevicesFree(@ASelParams);
end;
Добавьте к ActionList ещё один TAction с такими свойствами:
Свойство | Значение |
Caption | Disable |
Name | acEnable |
И добавим на панель ещё одну кнопку TButton, установив у неё следующие свойства:
Свойство | Значение |
Action | acEnable |
Name | btEnable |
В обработчике события OnUpdate для acEnable напишите:
procedure TfmMain.acEnableUpdate(Sender: TObject);
var SelectedNode: TTreeNode;
SelectedItem: TListItem;
begin
SelectedNode := TreeView.Selected;
SelectedItem := ListView.Selected;
TAction(Sender).Enabled := Assigned(SelectedNode) and
Assigned(SelectedItem) and
(SelectedNode.ImageIndex = -2);
end;
В обработчике OnExecute для acEnable вот такой код:
procedure TfmMain.acEnableExecute(Sender: TObject);
var GUID: TGUID;
begin
GUID := StringToGUID(ListView.Selected.Caption);
BluetoothSetServiceState(TreeView.Selected.Parent.ImageIndex,
BLUETOOTH_DEVICE_INFO(TreeView.Selected.Data^), GUID,
BLUETOOTH_SERVICE_DISABLE);
end;
После нажатия на кнопку btEnable сервис будет удалён из системы. Включить его можно будет через окно свойств устройства BlueTooth.
Добавьте в ActionList TAction со следующими свойствами:
Свойство | Значение |
Caption | Remove |
Name | acRemove |
И на Panel кнопку TButton со свойствами:
Свойство | Значение |
Action | acRemove |
Name | btRemove |
В обработчике событий OnUpdate для acRemove напишите:
procedure TfmMain.acRemoveUpdate(Sender: TObject);
begin
TAction(Sender).Enabled := acProperty.Enabled;
end;
В обработчике событий OnExecute такой код:
procedure TfmMain.acRemoveExecute(Sender: TObject);
var Info: BLUETOOTH_DEVICE_INFO;
Res: DWord;
begin
Info := BLUETOOTH_DEVICE_INFO(ListView.Selected.Data^);
Res := BluetoothRemoveDevice(Info.Address);
if Res <> ERROR_SUCCESS then
MessageDlg('Device not found', mtError, [mbOK], 0);
TreeViewChange(TreeView, TreeView.Selected);
end;
Процедура выполняется долго, так что не думайте, что программа зависла. Устройство удаляется из списка, но если уже иметь адрес устройства, то можно получить информацию о нём.
Добавьте TAction к ActionList и TButton на Panel со следующими свойствами соответственно:
Свойство | Значение | Caption | Update |
Name | acUpdate |
Свойство | Значение |
Action | acUpdate |
Name | btUpdate |
И вставьте этот код для обработчика события OnUpdate для acUpdate:
procedure TfmMain.acUpdateUpdate(Sender: TObject);
begin
TAction(Sender).Enabled := acProperty.Enabled;
end;
procedure TfmMain.acUpdateExecute(Sender: TObject);
var
Info: BLUETOOTH_DEVICE_INFO;
Res: DWord;
NewName: string;
begin
if InputQuery('Имя устройства', 'Новое имя', NewName) then
begin
lstrcpyW(Info.szName, PWideChar(WideString(NewName)));
Res := BluetoothUpdateDeviceRecord(Info);
if Res <> ERROR_SUCCESS then RaiseLastOsError;
TreeViewChange(TreeView, TreeView.Selected);
end;
end;
Полностью рабочий пример программы вы можете скачать здесь.
[+] Передача данных через BlueTooth:
Microsoft решила реализовать всю функциональность по передаче данных посредством Windows Socket Model (библиотека WinSock). Сейчас мы постараемся сделать простенький BlueTooth-клиент, который будет подсоединяться к удалённому как к модему и позволит вам выполнять AT-команды. Учтите, что данный клиент будет требовать авторизации устройств и не будет требовать наличия в системе каких-либо виртуальных COM-портов.
Сервисы и профили - это два краеугольных понятия BlueTooth. В некотором смысле они идентичны. Сервис - это приложение клиент-сервер, которое регистрирует определённым образом параметры в стеке протоколов BlueTooth. Наименование (GUID) всех сервисов строго определены BlueTooth.org Профиль - это соглашения и стандарты работы сервиса.
Создание клиента:
Прежде, чем использовать библиотеку WinSock, её необходимо инициализировать. Делается это вызовом функции WSAStartUp:
function WSAStartUp(wVersionRequired: Word; var lpWSAData: WSAData): integer; stdcall;
Для использования WinSock с BlueTooth необходимо указать в качестве параметра wVersionRequired указать номер $0202:
varData: WSADATA;
begin
if WSAStartUp($0202, Data) <> 0 then
raise Exception.Create(‘Winsock Initialization Failed.’);
По окончанию работы с WinSock библиотеку необходимо освободить. Для этого существует функция WSACleanUp:
function WSACleanUp: integer; stdcall;
Возвращаемое значение можно не проверять.
begin
WSACleanUp;
После того как библиотека инициализирована мы можем вызывать функции WinSock. Создайте простой Socket для работы с BlueTooth устройствами. Для этого необходимо вызвать функцию Socket:
function Socket(af, type_, protocol: integer): TSocket; stdcall;
Применяется она так:
varASocket: TSocket;
begin
ASocket := socket(AF_BTH, SOCK_STREAM, BTHPROTO_RFCOMM);
if ASocket = INVALID_SOCKET then RaiseLastOsError;
Функция вернёт корректный описатель сокета, либо INVALID_SOCKET в случае ошибки. Запомните, что BlueTooth поддерживает потоковые сокеты (SOCK_STREAM). Теперь необходимо заполнить структуру SOCKADDR_BTH. В эту структуру записывается информация о сервере, к которому нам нужно подключиться (адрес, сервис и т.п.). Делается это так:
var Addr: SOCKADDR_BTH;
AddrSize: DWORD;
begin
AddrSize := SizeOf(SOCKADDR_BTH);
FillChar(Addr, AddrSize, 0);
with Addr do
begin
addressFamily := AF_BTH;
btAddr := ADeviceAddress;
serviceClassId := SerialPortServiceClass_UUID;
port := DWORD(BT_PORT_ANY);
end;
Здесь в переменной ADeviceAddress должен быть адрес устройства (Int64), присоединяемся к любому порту (BT_PORT_ANY) сервиса SerialPortServiceClass. Далее вызываем функцию Connect, которая имеет вид:
function Connect(s: TSocket; name: PSockAddr; namelen: integer): integer; stdcall;
Используется она так:
begin
if connect(ASocket, @Addr, AddrSize) <> 0 then RaiseLastOsError;
Если функция выполнится успешно, то вернёт 0. В противно случае - значение отличное от 0. После того, как соединение установлено, можно передавать и принимать данные через сокет функциями send и recv.
function send(s: TSocket; var buf; len, flags: Integer): Integer; stdcall;
function recv(s: TSocket; var buf; len, flags: Integer): Integer; stdcall;
Функции возвращают количество переданных или принятых байт в случае успеха и отрицательное число в случае ошибки. Количество переданных или принятых байт может быть меньше, чем указанная в параметре len длина буфера. Тогда вам нужно повторить приём/передачу оставшихся байт. Ну и закрытие сокета осуществляется вызовом функции CloseSocket:
function closesocket(s: TSocket): Integer; stdcall;
Возвращаемое значение можно проигнорировать.
Создание сервера:
Как и создание клиента, создание сервера ничем не отличается от создания сервера для любой службы WinSock. Сокет создаётся также как и в примере для клиента, только в качестве адреса устройства указываем 0. Далее, необходимо привязать сокет к адресу. Делается это функцией bind:
function bind(s: TSocket; name: PSockAddr; namelen: Integer): Integer; stdcall;
Которая вызывается следующим образом:
begin
if Bind(ASocket, @Addr, AddrSize) <> 0 then RaiseLastOsError;
Далее вызываем функцию Listen, для того чтобы сервер начал прослушивать сокет на предмет подключения клиентов и функцию accept для приёма входящего подключения:
function listen(s: TSocket; backlog: Integer): Integer; stdcall;
function accept(s: TSocket; addr: PSockAddr; addrlen: PINT): TSocket; stdcall;
Делается это так:
var AClientSocket: TSocket;
begin
if listen(ASocket, 10) <> 0 then RaiseLastOSError;
AClientSocket = accept(ASocket, nil, nil);
После подключения клиента можно работать с AClientSocket - передавать и принимать данные. Если вы не желаете принимать входящие подключения, закройте слушающий сокет.
Здесь не рассматривались вопросы регистрации сервисов и протоколы верхнего уровня. Приведённой здесь информации достаточно для того, чтобы вы могли создавать приложение клиент, которое соединится с вашим телефоном по BlueTooth и сможет выполнять AT-команды.