Windows系统Ble蓝牙通信


实际应用

之前做的一个项目需求就是,windows系统的pc连接双模的蓝牙设备,根据已经连接的edr蓝牙去跟对应的ble蓝牙进行通信。
我的基本思路是,双模蓝牙设备的edr和ble的mac地址要一致或者要有对应关系,使用windows的api枚举出已经连接的edr设备并获取到mac地址,再根据mac地址去发现对应ble设备的实例地址。


方案

目前我了解到的,在windows上开发ble蓝牙有两种方案:

  1. 使用windows的api,有集成的开源库,这里提供连接:https://github.com/DerekGn/WinBle
  2. 使用uwp平台的api,有官方提供的demo,这里提供连接:https://learn.microsoft.com/zh-cn/windows/uwp/devices-sensors/gatt-client

我是选择的uwp平台的api,因为毕竟是官方的。
下面就按照上面提到的思路来一步步实现。

实现步骤

一、获取已经连接的Edr蓝牙设备的Mac地址

直接使用windows提供的api就可以实现了,贴代码。

#include <devguid.h>
#include <setupapi.h>
#include <comdef.h>

#pragma comment(lib, "Bthprops.lib")
#pragma comment(lib, "Setupapi.lib")

std::vector<BLUETOOTH_DEVICE_INFO> getConnectDevice()
{
	BLUETOOTH_DEVICE_SEARCH_PARAMS searchParams = { sizeof(BLUETOOTH_DEVICE_SEARCH_PARAMS) };
	BLUETOOTH_DEVICE_INFO deviceInfo = { sizeof(BLUETOOTH_DEVICE_INFO) };

	searchParams.fReturnAuthenticated = FALSE;
	searchParams.fReturnRemembered = FALSE;
	searchParams.fReturnConnected = TRUE;
	searchParams.fIssueInquiry = FALSE;
	searchParams.cTimeoutMultiplier = 2;

	HANDLE hDeviceFind = BluetoothFindFirstDevice(&searchParams, &deviceInfo);
	std::vector<BLUETOOTH_DEVICE_INFO> connectedDevices;

	if (hDeviceFind == NULL) {
		//std::cerr << "No connected Bluetooth devices found." << std::endl;
		return connectedDevices;
	}

	do {
		connectedDevices.push_back(deviceInfo);

		//std::wcout << L"Device Name: " << deviceInfo.szName << std::endl;
		//std::wcout << L"Device Address: " << deviceInfo.Address.ullLong << std::endl;
		//std::wcout << L"Device Class: 0x" << std::hex << deviceInfo.ulClassofDevice << std::dec << std::endl;
		//std::wcout << L"-------------------------------------" << std::endl;
	} while (BluetoothFindNextDevice(hDeviceFind, &deviceInfo));

	BluetoothFindDeviceClose(hDeviceFind);

	return connectedDevices;
}

可以通过配置 BLUETOOTH_DEVICE_SEARCH_PARAMS 这个结构体实现枚举不同状态的设备。两个结构体更详细的说明可以在微软官网找到。

二、根据Mac地址发现Ble蓝牙的实例路径

最主要的就是设置Ble Watcher的过滤器和Watcher的回调函数。

// 提供部分核心代码
#include <vector>
#include <thread>
#include <variant>
#include <iostream>
#include <Windows.h>
#include <bluetoothApis.h>
#include "ThirdPartyCustomDiscoveryInterface.h"
#include "winrt/Windows.Devices.Bluetooth.h"
#include "winrt/Windows.Devices.Enumeration.h"

enum FILTER_BT_TYPE
{
	FILTER_BT_NAME,
	FILTER_BT_MAC,
	FILTER_BT_CONNECT_STATUS,
	FILTER_BT_PAIR_STATUS,
	FILTER_BT_CUSTOM,
	FILTER_BT_NONE,
};

DeviceWatcher deviceWatcher = null;
hstring m_hsAqsFilter = L"(System.Devices.Aep.ProtocolId:=\"{bb7bb05e-5972-42b5-94fc-76eaa7084d49}\")";
event_token m_addEvent;
event_token m_updateEvent;

static void watcher_Added_Callback(DeviceWatcher sender, DeviceInformation deviceInfo)
{
	if (sender == deviceWatcher) {		
		std::string strDevName = to_string(deviceInfo.Name());
		std::string strId      = to_string(deviceInfo.Id());
		
		std::cout << "[Name]: " << strDevName << ", [Id]: " << strId << std::endl;
	}
}

static void watcher_Update_Callback(DeviceWatcher sender, DeviceInformationUpdate deviceInfoUpdate)
{
	if (sender == deviceWatcher) {
		//std::wcout << L"UPDATE: " << deviceInfoUpdate.Id().c_str() << std::endl;
	}
}

void setBtFilterImpl(FILTER_BT_TYPE type, std::variant<std::string, uint64_t, bool> val)
{
	switch (type)
	{
	case FILTER_BT_NAME: {
		hstring hsName = to_hstring(std::get<std::string>(val));
		m_hsAqsFilter = BluetoothLEDevice::GetDeviceSelectorFromDeviceName(hsName);
		break;
	}
	case FILTER_BT_MAC:
		m_hsAqsFilter = BluetoothLEDevice::GetDeviceSelectorFromBluetoothAddress(std::get<uint64_t>(val));
		break;
	case FILTER_BT_CONNECT_STATUS: {
		BluetoothConnectionStatus bStatus;
		if (std::get<bool>(val)) {
			bStatus = BluetoothConnectionStatus::Connected;
		}
		else {
			bStatus = BluetoothConnectionStatus::Disconnected;
		}
		m_hsAqsFilter = BluetoothLEDevice::GetDeviceSelectorFromConnectionStatus(bStatus);
		break;
	}
	case FILTER_BT_PAIR_STATUS:
		m_hsAqsFilter = BluetoothLEDevice::GetDeviceSelectorFromPairingState(std::get<bool>(val));
		break;
	case FILTER_BT_CUSTOM:
		m_hsAqsFilter = to_hstring(std::get<std::string>(val));
		break;
	case FILTER_BT_NONE:
		m_hsAqsFilter = L"(System.Devices.Aep.ProtocolId:=\"{bb7bb05e-5972-42b5-94fc-76eaa7084d49}\")";
		break;
	default:
		break;
	}
}

void startWatcher()
{
	m_knownDevices.Clear();
	auto requestedProperties = single_threaded_vector<hstring>({ L"System.Devices.Aep.DeviceAddress", L"System.Devices.Aep.IsConnected", L"System.Devices.Aep.Bluetooth.Le.IsConnectable" });

	deviceWatcher = DeviceInformation::CreateWatcher(
		m_hsAqsFilter,
		requestedProperties,
		DeviceInformationKind::AssociationEndpoint);

	m_addEvent = deviceWatcher.Added(watcher_Added_Callback);
	m_updateEvent = deviceWatcher.Updated(watcher_Update_Callback);
	deviceWatcher.Start();
}

bool searchBleByConnectEdr(const std::string& name)
{
	std::vector<BLUETOOTH_DEVICE_INFO> connectedDevices = getConnectDevice(); //上面的枚举函数

	uint64_t u64cbtMac = 0;
	for (const auto& d : connectedDevices) {
		std::string cbtName = to_string(d.szName);
		if (cbtName.compare(name) == 0) {
			u64cbtMac = d.Address.ullLong;
		}
	}

	if (u64cbtMac != 0) {
		setBtFilterImpl(FILTER_BT_MAC, u64cbtMac);
		startWatcher();
		return true;
	}

	return false;
}

调用 searchBleByConnectEdr 函数,参数设备蓝牙名。当函数返回值为 true 时,表明Wather开始寻找Ble蓝牙设备的实例路径,找到的设备信息会在 watcher_Added_Callback 这个回调函数获取到。
注意点 1:UWP平台需要用C++17及以上的标准。
注意点 2:Watcher的Add事件和Update事件都要设置回调函数,哪怕你不使用,否则Wather发现蓝牙的速度很慢。
注意点 3:DeviceInformation::CreateWatcher函数的第一、二个参数可以按自己需要求配置,上面代码中提供了事例。

三、Ble设备建立通信

经过上面两个步骤,如果不出意外的话可以拿到目标Ble设备的设备路径了,路径类似这样 BluetoothLE#BluetoothLEf0:f3:ae:95:3d:be-3d:e3:d1:b2:8c:76。其实这个路径在设备管理器里面也有类似的,在设备管理器中找到连接的蓝牙设备在 详细->属性 里面选择 Association Endpoint ID 会有一条路径 Bluetooth#Bluetoothf0:f3:ae:95:3d:be-3d:e3:d1:b2:8c:76 只是前面的 Bluetooth 变成了 BluetoothLE。如果能直接获取到 Association Endpoint ID 的值,就可以免去上面的步骤了。
拿到实例路径之后就可以建立通信了。

// 提供核心代码
#include <winrt/Windows.Devices.Bluetooth.h>
#include <winrt/Windows.Devices.Enumeration.h>
#include <winrt/Windows.Foundation.Collections.h>
#include <winrt/Windows.Devices.Bluetooth.GenericAttributeProfile.h>
#include <winrt/Windows.Storage.Streams.h>
#include <Windows.h>

using namespace winrt;
using namespace Windows::Foundation;
using namespace Windows::Foundation::Collections;
using namespace Windows::Devices::Enumeration;
using namespace Windows::Devices::Bluetooth;
using namespace Windows::Devices::Bluetooth::GenericAttributeProfile;
using namespace Windows::Storage::Streams;

Windows::Devices::Bluetooth::BluetoothLEDevice m_nBleDevice = nullptr;
GattCharacteristic m_TXCharacteristic = nullptr;
GattCharacteristic m_RXCharacteristic = nullptr;
event_token notificationsToken;

static const GUID UUID_A_SERVICE = { 0x00000000, 0x0000, 0x0000,{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } };
static const GUID UUID_TX_CHARACTERISTIC = { 0x00000000, 0x0000, 0x0000,{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } };
static const GUID UUID_RX_CHARACTERISTIC = { 0x00000000, 0x0000, 0x0000,{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } };

void HandleCallback(GattCharacteristic const&, GattValueChangedEventArgs args)
{
	std::string reply((char*)args.CharacteristicValue().data(), args.CharacteristicValue().Length());
	printf("Recv: ");
	for (int i = 0; i < reply.size(); i++) {
		printf("0x%02X ", (uint8_t)reply.at(i));
	}
	printf("\r\n");
}

void sendData(const std::string& data)
{
	DataWriter writer;
	writer.ByteOrder(ByteOrder::BigEndian);

	try {
		if (m_TXCharacteristic) {
			writer.WriteBytes(array_view<const uint8_t>((uint8_t*)data.data(), data.size());
			m_TXCharacteristic.WriteValueAsync(writer.DetachBuffer());
		}
	}
	catch (hresult_error& ex)
	{
		std::wcout << ex.message().c_str() << std::endl;
		throw;
	}
}

void connectAccessorieDevice(const std::string& devId) {
	try
	{
		m_nBleDevice = BluetoothLEDevice::FromIdAsync(winrt::to_hstring(devId)).get();
		if (m_nBleDevice == nullptr)
		{
			std::cout << "Failed to connect to device." << std::endl;
		}
	}
	catch (hresult_error& ex)
	{
		if (ex.to_abi() == HRESULT_FROM_WIN32(ERROR_DEVICE_NOT_AVAILABLE))
		{
			std::cout << "Bluetooth radio is not on." << std::endl;
		}
		else
		{
			throw;
		}
	}
	if (m_nBleDevice != nullptr)
	{	
		GattDeviceServicesResult result = m_nBleDevice.GetGattServicesAsync(BluetoothCacheMode::Uncached).get();
		//std::cout << "Connect result: " << (int)result.Status() << std::endl;
		if (result.Status() == GattCommunicationStatus::Success)
		{
			GattDeviceService dstService = nullptr;
			IVectorView<GattDeviceService> services = result.Services();
			//std::cout << "Found " << services.Size() << " services" << std::endl;
			for (auto&& service : services)
			{
				hstring serUuid = to_hstring(service.Uuid());
				//std::wcout << serUuid.c_str() << endl;
				if (service.Uuid().Data1 == UUID_A_SERVICE.Data1) {
					//std::cout << "========= MATCH ==========" << std::endl;
					//std::wcout << serUuid.c_str() << std::endl;
					dstService = service;
				}
			}

			IVectorView<GattCharacteristic> characteristics{ nullptr };
			try
			{
				auto accessStatus = dstService.RequestAccessAsync().get();
				if (accessStatus == DeviceAccessStatus::Allowed)
				{
					GattCharacteristicsResult result = dstService.GetCharacteristicsAsync(BluetoothCacheMode::Uncached).get();
					if (result.Status() == GattCommunicationStatus::Success)
					{
						characteristics = result.Characteristics();
					}
					else
					{
						std::cout << "Error accessing service.";
					}
				}
				else
				{
					std::cout << "Error accessing service.";
				}
			}
			catch (hresult_error& ex)
			{
				std::wcout << L"Restricted service. Can't read characteristics: " << ex.message().c_str();
			}

			if (characteristics)
			{
				for (GattCharacteristic&& c : characteristics)
				{
					//std::wcout << to_hstring(c.Uuid()).c_str() << std::endl;
					if (c.Uuid().Data1 == UUID_RX_CHARACTERISTIC.Data1) {
						//std::wcout << L"RX Characteristic: " << to_hstring(c.Uuid()).c_str() << std::endl;		
						m_RXCharacteristic = c;
						GattCommunicationStatus status = 
							m_RXCharacteristic.WriteClientCharacteristicConfigurationDescriptorAsync(GattClientCharacteristicConfigurationDescriptorValue::Notify).get();

						if (status == GattCommunicationStatus::Success) {
							notificationsToken = m_RXCharacteristic.ValueChanged(HandleCallback);
						}
					}
					else if (c.Uuid().Data1 == UUID_TX_CHARACTERISTIC.Data1) {
						std::wcout << L"TX Characteristic: " << to_hstring(c.Uuid()).c_str() << std::endl;
						m_TXCharacteristic = c;
					}
				}
			}
		}
		else
		{
			std::cout << "Device unreachable" << std::endl;
		}
	}
}

到这一步就可以正常与蓝牙设备进行Ble通信了。其实代码在官方提供的代码里面有,但都是异步写法的,我这里改成了同步的。感兴趣的可以自行去微软官网寻找一下 uwp平台ble蓝牙开发示例
如果没有了解过Ble蓝牙通信,这里估计看起来有点吃力。我这里是通过 notify 的方式获取回复的数据,还有获取特征值等函数,可以自行查阅官网示例。

完结撒花!

Windows蓝牙通信Bluetooth APIs是一组用于在Windows操作系统上管理和控制蓝牙设备通信的应用程序编程接口(API)。这些API允许开发人员创建应用程序,以便与蓝牙设备进行无线通信和交互。 使用Windows Bluetooth API,开发人员可以实现以下功能: 1. 设备发现和配对:通过调用适当的API函数,应用程序可以搜索附近的蓝牙设备并在需要时与之配对。配对是建立一个安全的蓝牙连接的必要步骤。 2. 数据传输:应用程序可以利用Bluetooth API在蓝牙设备之间传输数据。这可以用于传输文件、音频、视频等各种类型的数据。 3. 服务管理:应用程序可以使用API函数来管理蓝牙设备上的各种服务。它可以列出设备上支持的服务、连接到服务并进行相关操作。 4. Bluetooth低功耗:最新的Windows蓝牙API还支持蓝牙低功耗(Bluetooth Low Energy,BLE)技术,允许应用程序与诸如运动传感器、心率监测器等低功耗设备进行通信。 5. 错误处理和事件通知:Bluetooth API提供了适当的错误处理机制和事件通知,以便应用程序可以处理异常情况和响应蓝牙通信的状态更改。 综上所述,Windows蓝牙通信Bluetooth APIs提供了开发人员在Windows平台上实现蓝牙通信的功能,使他们能够轻松地创建与蓝牙设备交互的应用程序。无论是传输文件、进行数据交换还是实现与低功耗设备的通信,这些API都提供了必要的功能和工具。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值