实际应用
之前做的一个项目需求就是,windows系统的pc连接双模的蓝牙设备,根据已经连接的edr蓝牙去跟对应的ble蓝牙进行通信。
我的基本思路是,双模蓝牙设备的edr和ble的mac地址要一致或者要有对应关系,使用windows的api枚举出已经连接的edr设备并获取到mac地址,再根据mac地址去发现对应ble设备的实例地址。
方案
目前我了解到的,在windows上开发ble蓝牙有两种方案:
- 使用windows的api,有集成的开源库,这里提供连接:https://github.com/DerekGn/WinBle
- 使用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
的方式获取回复的数据,还有获取特征值等函数,可以自行查阅官网示例。
完结撒花!