818 lines
33 KiB
C++
818 lines
33 KiB
C++
|
|
#include "DRI/Dri_Ble.h"
|
|||
|
|
#include "DRI/Dri_Hid.h"
|
|||
|
|
#include "MID/Mid_Ble.h"
|
|||
|
|
#include <QtCore/QHash>
|
|||
|
|
#include <QtCore/QList>
|
|||
|
|
#include <QtCore/QMutex>
|
|||
|
|
#include <QtCore/QMutexLocker>
|
|||
|
|
#include <QtCore/QRegularExpression>
|
|||
|
|
#include <QtCore/QStringList>
|
|||
|
|
#include <QtCore/QUuid>
|
|||
|
|
#include <Windows.h>
|
|||
|
|
#include <SetupAPI.h>
|
|||
|
|
#include <bluetoothleapis.h>
|
|||
|
|
#include <bthledef.h>
|
|||
|
|
#include <hidsdi.h>
|
|||
|
|
#include <hidpi.h>
|
|||
|
|
#include <atomic>
|
|||
|
|
|
|||
|
|
#pragma comment(lib, "BluetoothAPIs.lib")
|
|||
|
|
#pragma comment(lib, "hid.lib")
|
|||
|
|
#pragma comment(lib, "setupapi.lib")
|
|||
|
|
|
|||
|
|
struct Dri_Ble_Struct_ServiceContext;
|
|||
|
|
struct Dri_Ble_Struct_HidPort;
|
|||
|
|
struct Dri_Ble_Struct_Subscription
|
|||
|
|
{
|
|||
|
|
Dri_Ble_Struct_Port* Port = nullptr;
|
|||
|
|
Dri_Ble_Struct_ServiceContext* Service = nullptr;
|
|||
|
|
BLUETOOTH_GATT_EVENT_HANDLE Event = nullptr;
|
|||
|
|
QString DeviceLabel;
|
|||
|
|
QString ServiceUuid;
|
|||
|
|
QString CharUuid;
|
|||
|
|
};
|
|||
|
|
struct Dri_Ble_Struct_Context
|
|||
|
|
{
|
|||
|
|
QMutex Mutex;
|
|||
|
|
QList<Mid_Struct_RawPacket> Queue;
|
|||
|
|
QVector<Dri_Ble_Struct_ServiceContext*> Services;
|
|||
|
|
QVector<Dri_Ble_Struct_HidPort*> Hids;
|
|||
|
|
std::atomic<long> CallbackCount { 0 };
|
|||
|
|
};
|
|||
|
|
struct Dri_Ble_Struct_ServiceContext
|
|||
|
|
{
|
|||
|
|
Dri_Ble_Struct_Context* Ctx = nullptr;
|
|||
|
|
HANDLE Handle = INVALID_HANDLE_VALUE;
|
|||
|
|
QString Uuid;
|
|||
|
|
QVector<Dri_Ble_Struct_Subscription*> Subs;
|
|||
|
|
};
|
|||
|
|
struct Dri_Ble_Struct_HidPort
|
|||
|
|
{
|
|||
|
|
Dri_Hid_Struct_ReadPort ReadPort;
|
|||
|
|
HANDLE WriteHandle = INVALID_HANDLE_VALUE;
|
|||
|
|
quint16 UsagePage = 0;
|
|||
|
|
quint16 Usage = 0;
|
|||
|
|
quint16 OutputLength = 0;
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
namespace
|
|||
|
|
{
|
|||
|
|
|
|||
|
|
struct ServiceIf { QString Path; QString InstanceId; QString Uuid; };
|
|||
|
|
struct DeviceIf { QString Path; QString InstanceId; QString Address; };
|
|||
|
|
struct HidIf
|
|||
|
|
{
|
|||
|
|
QString Path;
|
|||
|
|
QString InstanceId;
|
|||
|
|
quint16 VendorId = 0;
|
|||
|
|
quint16 ProductId = 0;
|
|||
|
|
quint16 UsagePage = 0;
|
|||
|
|
quint16 Usage = 0;
|
|||
|
|
quint16 InputLength = 0;
|
|||
|
|
quint16 OutputLength = 0;
|
|||
|
|
};
|
|||
|
|
struct PnpIf
|
|||
|
|
{
|
|||
|
|
QString Address;
|
|||
|
|
quint16 VendorId = 0;
|
|||
|
|
quint16 ProductId = 0;
|
|||
|
|
};
|
|||
|
|
const QString kSvcHidBrace = QStringLiteral("{00001812-0000-1000-8000-00805F9B34FB}");
|
|||
|
|
const QString kSvcCustom = QStringLiteral("0B7F5000-38D2-4F62-8F6F-36C4FD73A110");
|
|||
|
|
const QString kSvcDis = QStringLiteral("0000180A-0000-1000-8000-00805F9B34FB");
|
|||
|
|
const QString kChrPnpId = QStringLiteral("00002A50-0000-1000-8000-00805F9B34FB");
|
|||
|
|
const quint16 kUsagePageKeyboard = 0x0001;
|
|||
|
|
const quint16 kUsageKeyboard = 0x0006;
|
|||
|
|
|
|||
|
|
QString HResultText(HRESULT hr) { return QStringLiteral("0x%1").arg(static_cast<quint32>(hr), 8, 16, QLatin1Char('0')).toUpper(); }
|
|||
|
|
bool IsCommandHid(quint16 page, quint16 usage) { return (page == MID_CONST_USAGE_PAGE_VENDOR_COMMAND) && (usage == MID_CONST_USAGE_VENDOR_COMMAND); }
|
|||
|
|
bool IsUsefulHid(quint16 page, quint16 usage) { return ((page == kUsagePageKeyboard) && (usage == kUsageKeyboard)) || ((page == MID_CONST_USAGE_PAGE_CONSUMER) && (usage == MID_CONST_USAGE_CONSUMER)) || ((page == MID_CONST_USAGE_PAGE_VENDOR) && (usage == MID_CONST_USAGE_VENDOR)) || IsCommandHid(page, usage); }
|
|||
|
|
bool IsVendorHid(quint16 page, quint16 usage) { return (page == MID_CONST_USAGE_PAGE_VENDOR) && (usage == MID_CONST_USAGE_VENDOR); }
|
|||
|
|
Mid_Enum_RawPacketSource HidPacketSource(quint16 page, quint16 usage)
|
|||
|
|
{
|
|||
|
|
if ((page == kUsagePageKeyboard) && (usage == kUsageKeyboard)) return Mid_Enum_RawPacketSource_BleHidKeyboard;
|
|||
|
|
if ((page == MID_CONST_USAGE_PAGE_CONSUMER) && (usage == MID_CONST_USAGE_CONSUMER)) return Mid_Enum_RawPacketSource_BleHidConsumer;
|
|||
|
|
if (IsVendorHid(page, usage)) return Mid_Enum_RawPacketSource_BleHidVendor;
|
|||
|
|
if (IsCommandHid(page, usage)) return Mid_Enum_RawPacketSource_BleHidVendorCommand;
|
|||
|
|
return Mid_Enum_RawPacketSource_None;
|
|||
|
|
}
|
|||
|
|
QString ExtractAddress(const QString& text) { auto m = QRegularExpression(QStringLiteral("([0-9A-F]{12})(?=[\\\\&_]|$)")).match(text.toUpper()); return m.hasMatch() ? m.captured(1) : QString(); }
|
|||
|
|
QString ServiceUuidFromId(const QString& id) { auto m = QRegularExpression(QStringLiteral("\\{([0-9A-Fa-f-]{36})\\}")).match(id); return m.hasMatch() ? m.captured(1).toUpper() : QString(); }
|
|||
|
|
QString DeviceLabel(const QString& addr) { return addr.isEmpty() ? QStringLiteral("BLE") : QStringLiteral("BLE-%1").arg(addr); }
|
|||
|
|
QString FormatVidPidLabel(quint16 vendorId, quint16 productId)
|
|||
|
|
{
|
|||
|
|
return QStringLiteral("0x%1:0x%2")
|
|||
|
|
.arg(vendorId, 4, 16, QLatin1Char('0'))
|
|||
|
|
.arg(productId, 4, 16, QLatin1Char('0'))
|
|||
|
|
.toUpper();
|
|||
|
|
}
|
|||
|
|
QString PortName(const QString& dev, const QString& src) { const QString base = dev.isEmpty() ? QStringLiteral("Bluetooth") : QStringLiteral("Bluetooth(%1)").arg(dev); return src.isEmpty() ? base : QStringLiteral("%1/%2").arg(base, src); }
|
|||
|
|
QString HidSource(quint16 page, quint16 usage)
|
|||
|
|
{
|
|||
|
|
if ((page == kUsagePageKeyboard) && (usage == kUsageKeyboard)) return QStringLiteral("HID Keyboard");
|
|||
|
|
if ((page == MID_CONST_USAGE_PAGE_CONSUMER) && (usage == MID_CONST_USAGE_CONSUMER)) return QStringLiteral("HID Consumer");
|
|||
|
|
if ((page == MID_CONST_USAGE_PAGE_VENDOR) && (usage == MID_CONST_USAGE_VENDOR)) return QStringLiteral("HID Vendor");
|
|||
|
|
if (IsCommandHid(page, usage)) return QStringLiteral("HID Vendor Command");
|
|||
|
|
return QStringLiteral("HID UsagePage 0x%1 / Usage 0x%2").arg(page, 4, 16, QLatin1Char('0')).arg(usage, 4, 16, QLatin1Char('0')).toUpper();
|
|||
|
|
}
|
|||
|
|
QString DeviceInstanceId(HDEVINFO set, SP_DEVINFO_DATA* info)
|
|||
|
|
{
|
|||
|
|
DWORD need = 0;
|
|||
|
|
SetupDiGetDeviceInstanceIdW(set, info, nullptr, 0, &need);
|
|||
|
|
if (need == 0) return QString();
|
|||
|
|
QVector<wchar_t> buf(static_cast<int>(need) + 1, 0);
|
|||
|
|
return SetupDiGetDeviceInstanceIdW(set, info, buf.data(), static_cast<DWORD>(buf.size()), nullptr) ? QString::fromWCharArray(buf.constData()).trimmed() : QString();
|
|||
|
|
}
|
|||
|
|
bool InterfacePath(HDEVINFO set, SP_DEVICE_INTERFACE_DATA* itf, SP_DEVINFO_DATA* info, QString* path)
|
|||
|
|
{
|
|||
|
|
DWORD need = 0;
|
|||
|
|
SetupDiGetDeviceInterfaceDetailW(set, itf, nullptr, 0, &need, info);
|
|||
|
|
if (need == 0) return false;
|
|||
|
|
QByteArray buf(static_cast<int>(need), 0);
|
|||
|
|
auto* detail = reinterpret_cast<SP_DEVICE_INTERFACE_DETAIL_DATA_W*>(buf.data());
|
|||
|
|
detail->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA_W);
|
|||
|
|
if (!SetupDiGetDeviceInterfaceDetailW(set, itf, detail, need, nullptr, info)) return false;
|
|||
|
|
*path = QString::fromWCharArray(detail->DevicePath);
|
|||
|
|
return true;
|
|||
|
|
}
|
|||
|
|
QUuid QtUuid(const BTH_LE_UUID& uuid)
|
|||
|
|
{
|
|||
|
|
if (uuid.IsShortUuid) return QUuid(QStringLiteral("{%1-0000-1000-8000-00805F9B34FB}").arg(uuid.Value.ShortUuid, 4, 16, QLatin1Char('0')));
|
|||
|
|
const GUID& g = uuid.Value.LongUuid;
|
|||
|
|
return QUuid(g.Data1, g.Data2, g.Data3, g.Data4[0], g.Data4[1], g.Data4[2], g.Data4[3], g.Data4[4], g.Data4[5], g.Data4[6], g.Data4[7]);
|
|||
|
|
}
|
|||
|
|
QString CharText(const BTH_LE_GATT_CHARACTERISTIC& c)
|
|||
|
|
{
|
|||
|
|
const QString uuid = QtUuid(c.CharacteristicUuid).toString(QUuid::WithoutBraces).toUpper();
|
|||
|
|
return uuid == QStringLiteral("00000000-0000-0000-0000-000000000000") ? QStringLiteral("HANDLE 0x%1").arg(c.CharacteristicValueHandle, 4, 16, QLatin1Char('0')).toUpper() : uuid;
|
|||
|
|
}
|
|||
|
|
HANDLE OpenService(const QString& path)
|
|||
|
|
{
|
|||
|
|
HANDLE handle = CreateFileW(reinterpret_cast<LPCWSTR>(path.utf16()), GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, OPEN_EXISTING, 0, nullptr);
|
|||
|
|
return handle != INVALID_HANDLE_VALUE ? handle : CreateFileW(reinterpret_cast<LPCWSTR>(path.utf16()), GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, OPEN_EXISTING, 0, nullptr);
|
|||
|
|
}
|
|||
|
|
void QueuePacket(
|
|||
|
|
Dri_Ble_Struct_Context* ctx,
|
|||
|
|
Mid_Enum_RawPacketSource source,
|
|||
|
|
const QString& portName,
|
|||
|
|
const QByteArray& bytes)
|
|||
|
|
{
|
|||
|
|
if ((ctx == nullptr) || bytes.isEmpty()) return;
|
|||
|
|
Mid_Struct_RawPacket packet;
|
|||
|
|
packet.IsValid = true;
|
|||
|
|
packet.Source = source;
|
|||
|
|
packet.PortName = portName;
|
|||
|
|
packet.ByteArray = bytes;
|
|||
|
|
QMutexLocker lock(&ctx->Mutex); ctx->Queue.append(packet);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
QVector<ServiceIf> EnumServices()
|
|||
|
|
{
|
|||
|
|
QVector<ServiceIf> out;
|
|||
|
|
HDEVINFO set = SetupDiGetClassDevsW(&GUID_BLUETOOTH_GATT_SERVICE_DEVICE_INTERFACE, nullptr, nullptr, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE);
|
|||
|
|
if (set == INVALID_HANDLE_VALUE) return out;
|
|||
|
|
SP_DEVICE_INTERFACE_DATA itf = { sizeof(SP_DEVICE_INTERFACE_DATA) };
|
|||
|
|
for (DWORD i = 0; SetupDiEnumDeviceInterfaces(set, nullptr, &GUID_BLUETOOTH_GATT_SERVICE_DEVICE_INTERFACE, i, &itf); ++i)
|
|||
|
|
{
|
|||
|
|
SP_DEVINFO_DATA info = { sizeof(SP_DEVINFO_DATA) };
|
|||
|
|
QString path;
|
|||
|
|
if (!InterfacePath(set, &itf, &info, &path)) continue;
|
|||
|
|
ServiceIf item; item.Path = path; item.InstanceId = DeviceInstanceId(set, &info); item.Uuid = ServiceUuidFromId(item.InstanceId); out.append(item);
|
|||
|
|
}
|
|||
|
|
SetupDiDestroyDeviceInfoList(set);
|
|||
|
|
return out;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
QVector<DeviceIf> EnumBleDevices()
|
|||
|
|
{
|
|||
|
|
QVector<DeviceIf> out;
|
|||
|
|
HDEVINFO set = SetupDiGetClassDevsW(
|
|||
|
|
&GUID_BLUETOOTHLE_DEVICE_INTERFACE,
|
|||
|
|
nullptr,
|
|||
|
|
nullptr,
|
|||
|
|
DIGCF_PRESENT | DIGCF_DEVICEINTERFACE);
|
|||
|
|
if (set == INVALID_HANDLE_VALUE) return out;
|
|||
|
|
|
|||
|
|
SP_DEVICE_INTERFACE_DATA itf = { sizeof(SP_DEVICE_INTERFACE_DATA) };
|
|||
|
|
for (DWORD i = 0; SetupDiEnumDeviceInterfaces(set, nullptr, &GUID_BLUETOOTHLE_DEVICE_INTERFACE, i, &itf); ++i)
|
|||
|
|
{
|
|||
|
|
SP_DEVINFO_DATA info = { sizeof(SP_DEVINFO_DATA) };
|
|||
|
|
QString path;
|
|||
|
|
if (!InterfacePath(set, &itf, &info, &path)) continue;
|
|||
|
|
|
|||
|
|
DeviceIf item;
|
|||
|
|
item.Path = path;
|
|||
|
|
item.InstanceId = DeviceInstanceId(set, &info);
|
|||
|
|
item.Address = ExtractAddress(item.InstanceId);
|
|||
|
|
if (item.Address.isEmpty()) continue;
|
|||
|
|
out.append(item);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
SetupDiDestroyDeviceInfoList(set);
|
|||
|
|
return out;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
QVector<HidIf> EnumBleHids()
|
|||
|
|
{
|
|||
|
|
QVector<HidIf> out;
|
|||
|
|
GUID hidGuid; HidD_GetHidGuid(&hidGuid);
|
|||
|
|
HDEVINFO set = SetupDiGetClassDevsW(&hidGuid, nullptr, nullptr, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE);
|
|||
|
|
if (set == INVALID_HANDLE_VALUE) return out;
|
|||
|
|
SP_DEVICE_INTERFACE_DATA itf = { sizeof(SP_DEVICE_INTERFACE_DATA) };
|
|||
|
|
for (DWORD i = 0; SetupDiEnumDeviceInterfaces(set, nullptr, &hidGuid, i, &itf); ++i)
|
|||
|
|
{
|
|||
|
|
SP_DEVINFO_DATA info = { sizeof(SP_DEVINFO_DATA) };
|
|||
|
|
QString path;
|
|||
|
|
if (!InterfacePath(set, &itf, &info, &path)) continue;
|
|||
|
|
const QString id = DeviceInstanceId(set, &info).toUpper();
|
|||
|
|
if (!id.contains(kSvcHidBrace)) continue;
|
|||
|
|
HANDLE handle = CreateFileW(reinterpret_cast<LPCWSTR>(path.utf16()), 0, FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, OPEN_EXISTING, 0, nullptr);
|
|||
|
|
if (handle == INVALID_HANDLE_VALUE) continue;
|
|||
|
|
HIDD_ATTRIBUTES attrs = {};
|
|||
|
|
attrs.Size = sizeof(attrs);
|
|||
|
|
PHIDP_PREPARSED_DATA prep = nullptr;
|
|||
|
|
HIDP_CAPS caps = {};
|
|||
|
|
const bool ok =
|
|||
|
|
HidD_GetAttributes(handle, &attrs) &&
|
|||
|
|
HidD_GetPreparsedData(handle, &prep) &&
|
|||
|
|
(HidP_GetCaps(prep, &caps) == HIDP_STATUS_SUCCESS) &&
|
|||
|
|
IsUsefulHid(caps.UsagePage, caps.Usage) &&
|
|||
|
|
(caps.InputReportByteLength > 0);
|
|||
|
|
if (prep != nullptr) HidD_FreePreparsedData(prep);
|
|||
|
|
CloseHandle(handle);
|
|||
|
|
if (!ok) continue;
|
|||
|
|
HidIf item;
|
|||
|
|
item.Path = path;
|
|||
|
|
item.InstanceId = id;
|
|||
|
|
item.VendorId = attrs.VendorID;
|
|||
|
|
item.ProductId = attrs.ProductID;
|
|||
|
|
item.UsagePage = caps.UsagePage;
|
|||
|
|
item.Usage = caps.Usage;
|
|||
|
|
item.InputLength = caps.InputReportByteLength;
|
|||
|
|
item.OutputLength = caps.OutputReportByteLength;
|
|||
|
|
out.append(item);
|
|||
|
|
}
|
|||
|
|
SetupDiDestroyDeviceInfoList(set);
|
|||
|
|
return out;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
QVector<BTH_LE_GATT_CHARACTERISTIC> Characteristics(HANDLE handle)
|
|||
|
|
{
|
|||
|
|
USHORT count = 0;
|
|||
|
|
HRESULT hr = BluetoothGATTGetCharacteristics(handle, nullptr, 0, nullptr, &count, BLUETOOTH_GATT_FLAG_NONE);
|
|||
|
|
if (FAILED(hr) && (hr != HRESULT_FROM_WIN32(ERROR_MORE_DATA))) return {};
|
|||
|
|
QVector<BTH_LE_GATT_CHARACTERISTIC> out(count);
|
|||
|
|
hr = BluetoothGATTGetCharacteristics(handle, nullptr, count, out.data(), &count, BLUETOOTH_GATT_FLAG_NONE);
|
|||
|
|
if (FAILED(hr)) return {};
|
|||
|
|
out.resize(count); return out;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
QVector<BTH_LE_GATT_DESCRIPTOR> Descriptors(HANDLE handle, const BTH_LE_GATT_CHARACTERISTIC& c)
|
|||
|
|
{
|
|||
|
|
USHORT count = 0;
|
|||
|
|
HRESULT hr = BluetoothGATTGetDescriptors(handle, const_cast<PBTH_LE_GATT_CHARACTERISTIC>(&c), 0, nullptr, &count, BLUETOOTH_GATT_FLAG_NONE);
|
|||
|
|
if (FAILED(hr) && (hr != HRESULT_FROM_WIN32(ERROR_MORE_DATA))) return {};
|
|||
|
|
QVector<BTH_LE_GATT_DESCRIPTOR> out(count);
|
|||
|
|
hr = BluetoothGATTGetDescriptors(handle, const_cast<PBTH_LE_GATT_CHARACTERISTIC>(&c), count, out.data(), &count, BLUETOOTH_GATT_FLAG_NONE);
|
|||
|
|
if (FAILED(hr)) return {};
|
|||
|
|
out.resize(count); return out;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
bool ReadValue(HANDLE handle, const BTH_LE_GATT_CHARACTERISTIC& c, QByteArray* out)
|
|||
|
|
{
|
|||
|
|
USHORT need = 0;
|
|||
|
|
HRESULT hr = BluetoothGATTGetCharacteristicValue(handle, const_cast<PBTH_LE_GATT_CHARACTERISTIC>(&c), 0, nullptr, &need, BLUETOOTH_GATT_FLAG_FORCE_READ_FROM_DEVICE);
|
|||
|
|
if (FAILED(hr) && (hr != HRESULT_FROM_WIN32(ERROR_MORE_DATA))) return false;
|
|||
|
|
if (need == 0) return false;
|
|||
|
|
QByteArray buffer(static_cast<int>(need), 0);
|
|||
|
|
auto* value = reinterpret_cast<PBTH_LE_GATT_CHARACTERISTIC_VALUE>(buffer.data());
|
|||
|
|
hr = BluetoothGATTGetCharacteristicValue(handle, const_cast<PBTH_LE_GATT_CHARACTERISTIC>(&c), need, value, nullptr, BLUETOOTH_GATT_FLAG_FORCE_READ_FROM_DEVICE);
|
|||
|
|
if (FAILED(hr)) return false;
|
|||
|
|
*out = QByteArray(reinterpret_cast<const char*>(value->Data), static_cast<int>(value->DataSize));
|
|||
|
|
return true;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
VOID CALLBACK OnGattEvent(BTH_LE_GATT_EVENT_TYPE type, PVOID out, PVOID context)
|
|||
|
|
{
|
|||
|
|
auto* sub = static_cast<Dri_Ble_Struct_Subscription*>(context);
|
|||
|
|
if ((sub == nullptr) || (sub->Service == nullptr) || (sub->Service->Ctx == nullptr)) return;
|
|||
|
|
Dri_Ble_Struct_Context* ctx = sub->Service->Ctx;
|
|||
|
|
ctx->CallbackCount.fetch_add(1, std::memory_order_acq_rel);
|
|||
|
|
if ((type == CharacteristicValueChangedEvent) && (out != nullptr) && (sub->Port != nullptr))
|
|||
|
|
{
|
|||
|
|
const auto* evt = static_cast<const BLUETOOTH_GATT_VALUE_CHANGED_EVENT*>(out);
|
|||
|
|
if ((evt->CharacteristicValue != nullptr) && (evt->CharacteristicValue->DataSize > 0)) QueuePacket(ctx, Mid_Enum_RawPacketSource_BleGatt, PortName(sub->DeviceLabel, QStringLiteral("SVC %1 / CHR %2").arg(sub->ServiceUuid, sub->CharUuid)), QByteArray(reinterpret_cast<const char*>(evt->CharacteristicValue->Data), static_cast<int>(evt->CharacteristicValue->DataSize)));
|
|||
|
|
}
|
|||
|
|
ctx->CallbackCount.fetch_sub(1, std::memory_order_acq_rel);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
bool Subscribe(Dri_Ble_Struct_Port* port, Dri_Ble_Struct_ServiceContext* svc, const QString& deviceLabel, const BTH_LE_GATT_CHARACTERISTIC& c, QString* error)
|
|||
|
|
{
|
|||
|
|
auto* sub = new Dri_Ble_Struct_Subscription();
|
|||
|
|
sub->Port = port; sub->Service = svc; sub->DeviceLabel = deviceLabel; sub->ServiceUuid = svc->Uuid; sub->CharUuid = CharText(c);
|
|||
|
|
BLUETOOTH_GATT_VALUE_CHANGED_EVENT_REGISTRATION reg = {}; reg.NumCharacteristics = 1; reg.Characteristics[0] = c;
|
|||
|
|
HRESULT hr = BluetoothGATTRegisterEvent(svc->Handle, CharacteristicValueChangedEvent, ®, OnGattEvent, sub, &sub->Event, BLUETOOTH_GATT_FLAG_NONE);
|
|||
|
|
if (FAILED(hr)) { if (error != nullptr) *error = QStringLiteral("BLE notify register failed: %1 / %2 / %3").arg(svc->Uuid, sub->CharUuid, HResultText(hr)); delete sub; return false; }
|
|||
|
|
bool configured = false;
|
|||
|
|
for (const BTH_LE_GATT_DESCRIPTOR& d : Descriptors(svc->Handle, c))
|
|||
|
|
{
|
|||
|
|
if (d.DescriptorType != ClientCharacteristicConfiguration) continue;
|
|||
|
|
BTH_LE_GATT_DESCRIPTOR_VALUE value = {}; value.DescriptorType = ClientCharacteristicConfiguration;
|
|||
|
|
value.ClientCharacteristicConfiguration.IsSubscribeToNotification = c.IsNotifiable;
|
|||
|
|
value.ClientCharacteristicConfiguration.IsSubscribeToIndication = c.IsIndicatable;
|
|||
|
|
if (SUCCEEDED(BluetoothGATTSetDescriptorValue(svc->Handle, const_cast<PBTH_LE_GATT_DESCRIPTOR>(&d), &value, BLUETOOTH_GATT_FLAG_NONE))) { configured = true; break; }
|
|||
|
|
}
|
|||
|
|
if (!configured)
|
|||
|
|
{
|
|||
|
|
if (sub->Event != nullptr) BluetoothGATTUnregisterEvent(sub->Event, BLUETOOTH_GATT_FLAG_NONE);
|
|||
|
|
if (error != nullptr) *error = QStringLiteral("BLE notify enable failed: %1 / %2").arg(svc->Uuid, sub->CharUuid);
|
|||
|
|
delete sub;
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
svc->Subs.append(sub);
|
|||
|
|
return true;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
bool AttachService(Dri_Ble_Struct_Port* port, Dri_Ble_Struct_Context* ctx, const ServiceIf& item, const QString& deviceLabel, int* charCount, int* subCount)
|
|||
|
|
{
|
|||
|
|
auto* svc = new Dri_Ble_Struct_ServiceContext();
|
|||
|
|
svc->Ctx = ctx; svc->Uuid = item.Uuid; svc->Handle = OpenService(item.Path);
|
|||
|
|
if (svc->Handle == INVALID_HANDLE_VALUE) { delete svc; return false; }
|
|||
|
|
const auto list = Characteristics(svc->Handle);
|
|||
|
|
if (list.isEmpty()) { CloseHandle(svc->Handle); delete svc; return false; }
|
|||
|
|
bool keep = false;
|
|||
|
|
for (const BTH_LE_GATT_CHARACTERISTIC& c : list)
|
|||
|
|
{
|
|||
|
|
if (!c.IsReadable && !c.IsNotifiable && !c.IsIndicatable) continue;
|
|||
|
|
const QString charUuid = CharText(c);
|
|||
|
|
++(*charCount);
|
|||
|
|
if (c.IsNotifiable || c.IsIndicatable) { if (Subscribe(port, svc, deviceLabel, c, nullptr)) { keep = true; ++(*subCount); } }
|
|||
|
|
if (c.IsReadable) { QByteArray bytes; if (ReadValue(svc->Handle, c, &bytes) && !bytes.isEmpty()) QueuePacket(ctx, Mid_Enum_RawPacketSource_BleGatt, PortName(deviceLabel, QStringLiteral("SVC %1 / CHR %2").arg(item.Uuid, charUuid)), bytes); }
|
|||
|
|
}
|
|||
|
|
if (keep) { ctx->Services.append(svc); return true; }
|
|||
|
|
CloseHandle(svc->Handle);
|
|||
|
|
delete svc;
|
|||
|
|
return true;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
void CloseHidPort(Dri_Ble_Struct_HidPort* hid)
|
|||
|
|
{
|
|||
|
|
Dri_Hid_CloseReadPort(&hid->ReadPort);
|
|||
|
|
if ((hid->WriteHandle != nullptr) && (hid->WriteHandle != INVALID_HANDLE_VALUE)) CloseHandle(hid->WriteHandle);
|
|||
|
|
delete hid;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
bool OpenHidPort(Dri_Ble_Struct_Context* ctx, const HidIf& item, const QString& deviceLabel)
|
|||
|
|
{
|
|||
|
|
auto* hid = new Dri_Ble_Struct_HidPort();
|
|||
|
|
const QString source = HidSource(item.UsagePage, item.Usage);
|
|||
|
|
hid->UsagePage = item.UsagePage;
|
|||
|
|
hid->Usage = item.Usage;
|
|||
|
|
hid->OutputLength = item.OutputLength;
|
|||
|
|
QString textError;
|
|||
|
|
if (!Dri_Hid_InitReadPort(
|
|||
|
|
&hid->ReadPort,
|
|||
|
|
item.Path,
|
|||
|
|
item.InputLength,
|
|||
|
|
HidPacketSource(item.UsagePage, item.Usage),
|
|||
|
|
PortName(deviceLabel, source),
|
|||
|
|
&textError))
|
|||
|
|
{
|
|||
|
|
delete hid;
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
if (item.OutputLength > 0)
|
|||
|
|
{
|
|||
|
|
hid->WriteHandle = Dri_Hid_OpenWriteHandle(item.Path, nullptr);
|
|||
|
|
}
|
|||
|
|
ctx->Hids.append(hid);
|
|||
|
|
return true;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
QString FormatAddressLabel(const QString& Address)
|
|||
|
|
{
|
|||
|
|
const QString Formatted = Mid_FormatBleAddress(Address);
|
|||
|
|
return Formatted.isEmpty() ? QStringLiteral("(unknown)") : Formatted;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
bool ParsePnpIdValue(const QByteArray& bytes, quint16* vendorId, quint16* productId)
|
|||
|
|
{
|
|||
|
|
if (bytes.size() < 7) return false;
|
|||
|
|
const quint8 vendorSource = static_cast<quint8>(bytes.at(0));
|
|||
|
|
// Only accept USB-IF sourced PnP IDs so the fields map directly to USB VID/PID.
|
|||
|
|
if (vendorSource != 0x02U) return false;
|
|||
|
|
*vendorId =
|
|||
|
|
static_cast<quint8>(bytes.at(1)) |
|
|||
|
|
(static_cast<quint16>(static_cast<quint8>(bytes.at(2))) << 8);
|
|||
|
|
*productId =
|
|||
|
|
static_cast<quint8>(bytes.at(3)) |
|
|||
|
|
(static_cast<quint16>(static_cast<quint8>(bytes.at(4))) << 8);
|
|||
|
|
return true;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
QVector<PnpIf> EnumPnps(const QVector<DeviceIf>& deviceList, QStringList* p_DebugList)
|
|||
|
|
{
|
|||
|
|
QVector<PnpIf> out;
|
|||
|
|
for (const DeviceIf& device : deviceList)
|
|||
|
|
{
|
|||
|
|
HANDLE handle = OpenService(device.Path);
|
|||
|
|
if (handle == INVALID_HANDLE_VALUE)
|
|||
|
|
{
|
|||
|
|
if (p_DebugList != nullptr)
|
|||
|
|
{
|
|||
|
|
p_DebugList->append(
|
|||
|
|
QStringLiteral("PnP: 打开 BLE 设备失败 %1")
|
|||
|
|
.arg(FormatAddressLabel(device.Address)));
|
|||
|
|
}
|
|||
|
|
continue;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
USHORT serviceCount = 0;
|
|||
|
|
HRESULT hr = BluetoothGATTGetServices(
|
|||
|
|
handle,
|
|||
|
|
0,
|
|||
|
|
nullptr,
|
|||
|
|
&serviceCount,
|
|||
|
|
BLUETOOTH_GATT_FLAG_NONE);
|
|||
|
|
if (FAILED(hr) && (hr != HRESULT_FROM_WIN32(ERROR_MORE_DATA)))
|
|||
|
|
{
|
|||
|
|
if (p_DebugList != nullptr)
|
|||
|
|
{
|
|||
|
|
p_DebugList->append(
|
|||
|
|
QStringLiteral("PnP: 枚举服务失败 %1 / hr=%2")
|
|||
|
|
.arg(FormatAddressLabel(device.Address), HResultText(hr)));
|
|||
|
|
}
|
|||
|
|
CloseHandle(handle);
|
|||
|
|
continue;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
QVector<BTH_LE_GATT_SERVICE> services(serviceCount);
|
|||
|
|
hr = BluetoothGATTGetServices(
|
|||
|
|
handle,
|
|||
|
|
serviceCount,
|
|||
|
|
services.data(),
|
|||
|
|
&serviceCount,
|
|||
|
|
BLUETOOTH_GATT_FLAG_NONE);
|
|||
|
|
if (FAILED(hr))
|
|||
|
|
{
|
|||
|
|
if (p_DebugList != nullptr)
|
|||
|
|
{
|
|||
|
|
p_DebugList->append(
|
|||
|
|
QStringLiteral("PnP: 读取服务列表失败 %1 / hr=%2")
|
|||
|
|
.arg(FormatAddressLabel(device.Address), HResultText(hr)));
|
|||
|
|
}
|
|||
|
|
CloseHandle(handle);
|
|||
|
|
continue;
|
|||
|
|
}
|
|||
|
|
services.resize(serviceCount);
|
|||
|
|
|
|||
|
|
QByteArray valueBytes;
|
|||
|
|
bool found = false;
|
|||
|
|
quint16 vendorId = 0;
|
|||
|
|
quint16 productId = 0;
|
|||
|
|
QStringList serviceUuidList;
|
|||
|
|
QStringList charUuidList;
|
|||
|
|
bool hasPnpChar = false;
|
|||
|
|
|
|||
|
|
for (const BTH_LE_GATT_SERVICE& service : services)
|
|||
|
|
{
|
|||
|
|
const QUuid serviceUuid = QtUuid(service.ServiceUuid);
|
|||
|
|
const QString serviceUuidText =
|
|||
|
|
serviceUuid.toString(QUuid::WithoutBraces).toUpper();
|
|||
|
|
serviceUuidList.append(serviceUuidText);
|
|||
|
|
|
|||
|
|
USHORT charCount = 0;
|
|||
|
|
hr = BluetoothGATTGetCharacteristics(
|
|||
|
|
handle,
|
|||
|
|
const_cast<PBTH_LE_GATT_SERVICE>(&service),
|
|||
|
|
0,
|
|||
|
|
nullptr,
|
|||
|
|
&charCount,
|
|||
|
|
BLUETOOTH_GATT_FLAG_NONE);
|
|||
|
|
if (FAILED(hr) && (hr != HRESULT_FROM_WIN32(ERROR_MORE_DATA)))
|
|||
|
|
{
|
|||
|
|
if (p_DebugList != nullptr)
|
|||
|
|
{
|
|||
|
|
p_DebugList->append(
|
|||
|
|
QStringLiteral("PnP: 枚举 180A 特征失败 %1 / hr=%2")
|
|||
|
|
.arg(FormatAddressLabel(device.Address), HResultText(hr)));
|
|||
|
|
}
|
|||
|
|
break;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
QVector<BTH_LE_GATT_CHARACTERISTIC> chars(charCount);
|
|||
|
|
hr = BluetoothGATTGetCharacteristics(
|
|||
|
|
handle,
|
|||
|
|
const_cast<PBTH_LE_GATT_SERVICE>(&service),
|
|||
|
|
charCount,
|
|||
|
|
chars.data(),
|
|||
|
|
&charCount,
|
|||
|
|
BLUETOOTH_GATT_FLAG_NONE);
|
|||
|
|
if (FAILED(hr))
|
|||
|
|
{
|
|||
|
|
if (p_DebugList != nullptr)
|
|||
|
|
{
|
|||
|
|
p_DebugList->append(
|
|||
|
|
QStringLiteral("PnP: 读取 180A 特征失败 %1 / hr=%2")
|
|||
|
|
.arg(FormatAddressLabel(device.Address), HResultText(hr)));
|
|||
|
|
}
|
|||
|
|
break;
|
|||
|
|
}
|
|||
|
|
chars.resize(charCount);
|
|||
|
|
|
|||
|
|
for (const BTH_LE_GATT_CHARACTERISTIC& characteristic : chars)
|
|||
|
|
{
|
|||
|
|
const QString charUuid = CharText(characteristic);
|
|||
|
|
charUuidList.append(QStringLiteral("%1/%2").arg(serviceUuidText, charUuid));
|
|||
|
|
if (charUuid != kChrPnpId) continue;
|
|||
|
|
hasPnpChar = true;
|
|||
|
|
if (!ReadValue(handle, characteristic, &valueBytes))
|
|||
|
|
{
|
|||
|
|
if (p_DebugList != nullptr)
|
|||
|
|
{
|
|||
|
|
p_DebugList->append(
|
|||
|
|
QStringLiteral("PnP: 发现 2A50 但读取失败 %1 / svc=%2")
|
|||
|
|
.arg(FormatAddressLabel(device.Address), serviceUuidText));
|
|||
|
|
}
|
|||
|
|
break;
|
|||
|
|
}
|
|||
|
|
found = ParsePnpIdValue(valueBytes, &vendorId, &productId);
|
|||
|
|
if (p_DebugList != nullptr)
|
|||
|
|
{
|
|||
|
|
p_DebugList->append(
|
|||
|
|
QStringLiteral("PnP: %1 / svc=%2 / raw=%3 / parsed=%4")
|
|||
|
|
.arg(FormatAddressLabel(device.Address))
|
|||
|
|
.arg(serviceUuidText)
|
|||
|
|
.arg(Mid_GetHexText(valueBytes))
|
|||
|
|
.arg(found
|
|||
|
|
? FormatVidPidLabel(vendorId, productId)
|
|||
|
|
: QStringLiteral("invalid")));
|
|||
|
|
}
|
|||
|
|
break;
|
|||
|
|
}
|
|||
|
|
break;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
CloseHandle(handle);
|
|||
|
|
if (!hasPnpChar && (p_DebugList != nullptr))
|
|||
|
|
{
|
|||
|
|
p_DebugList->append(
|
|||
|
|
QStringLiteral("PnP: 未发现 2A50 %1 / services=%2 / chars=%3")
|
|||
|
|
.arg(FormatAddressLabel(device.Address))
|
|||
|
|
.arg(serviceUuidList.join(QStringLiteral(", ")))
|
|||
|
|
.arg(charUuidList.join(QStringLiteral(", "))));
|
|||
|
|
}
|
|||
|
|
if (!found) continue;
|
|||
|
|
|
|||
|
|
bool exists = false;
|
|||
|
|
for (const PnpIf& item : out)
|
|||
|
|
{
|
|||
|
|
if (item.Address == device.Address)
|
|||
|
|
{
|
|||
|
|
exists = true;
|
|||
|
|
break;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
if (exists) continue;
|
|||
|
|
|
|||
|
|
PnpIf item;
|
|||
|
|
item.Address = device.Address;
|
|||
|
|
item.VendorId = vendorId;
|
|||
|
|
item.ProductId = productId;
|
|||
|
|
out.append(item);
|
|||
|
|
}
|
|||
|
|
return out;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
QStringList CollectPnpAddresses(
|
|||
|
|
const QVector<PnpIf>& pnpList,
|
|||
|
|
quint16 vendorId,
|
|||
|
|
quint16 productId)
|
|||
|
|
{
|
|||
|
|
QStringList addressList;
|
|||
|
|
for (const PnpIf& pnp : pnpList)
|
|||
|
|
{
|
|||
|
|
if ((pnp.VendorId == vendorId) && (pnp.ProductId == productId) &&
|
|||
|
|
!addressList.contains(pnp.Address))
|
|||
|
|
{
|
|||
|
|
addressList.append(pnp.Address);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
return addressList;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
QStringList CollectHidAddresses(const QVector<HidIf>& HidList)
|
|||
|
|
{
|
|||
|
|
QStringList AddressList;
|
|||
|
|
for (const HidIf& Hid : HidList)
|
|||
|
|
{
|
|||
|
|
const QString Address = ExtractAddress(Hid.InstanceId);
|
|||
|
|
if (!Address.isEmpty() && !AddressList.contains(Address)) AddressList.append(Address);
|
|||
|
|
}
|
|||
|
|
return AddressList;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
QString BuildCandidateSummary(
|
|||
|
|
const QVector<PnpIf>& pnpList,
|
|||
|
|
const QVector<HidIf>& HidList,
|
|||
|
|
const QVector<ServiceIf>& ServiceList,
|
|||
|
|
const QStringList& AllowedAddressList)
|
|||
|
|
{
|
|||
|
|
QHash<QString, int> HidCountMap;
|
|||
|
|
QHash<QString, int> GattCountMap;
|
|||
|
|
QHash<QString, QString> PnpTextMap;
|
|||
|
|
for (const PnpIf& pnp : pnpList)
|
|||
|
|
{
|
|||
|
|
PnpTextMap.insert(
|
|||
|
|
pnp.Address,
|
|||
|
|
FormatVidPidLabel(pnp.VendorId, pnp.ProductId));
|
|||
|
|
}
|
|||
|
|
for (const HidIf& Hid : HidList)
|
|||
|
|
{
|
|||
|
|
const QString Address = ExtractAddress(Hid.InstanceId);
|
|||
|
|
if (!Address.isEmpty()) ++HidCountMap[Address];
|
|||
|
|
}
|
|||
|
|
for (const ServiceIf& Service : ServiceList)
|
|||
|
|
{
|
|||
|
|
if (Service.Uuid != kSvcCustom) continue;
|
|||
|
|
const QString Address = ExtractAddress(Service.InstanceId);
|
|||
|
|
if (!Address.isEmpty() && AllowedAddressList.contains(Address)) ++GattCountMap[Address];
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
QStringList AddressList = HidCountMap.keys();
|
|||
|
|
for (const QString& Address : GattCountMap.keys()) if (!AddressList.contains(Address)) AddressList.append(Address);
|
|||
|
|
if (AddressList.isEmpty()) return QStringLiteral("无");
|
|||
|
|
|
|||
|
|
std::sort(AddressList.begin(), AddressList.end());
|
|||
|
|
QStringList SummaryList;
|
|||
|
|
for (const QString& Address : AddressList)
|
|||
|
|
{
|
|||
|
|
SummaryList.append(
|
|||
|
|
QStringLiteral("%1 [pnp=%2, hid=%3, gatt=%4]")
|
|||
|
|
.arg(FormatAddressLabel(Address))
|
|||
|
|
.arg(PnpTextMap.value(Address, QStringLiteral("unknown")))
|
|||
|
|
.arg(HidCountMap.value(Address))
|
|||
|
|
.arg(GattCountMap.value(Address)));
|
|||
|
|
}
|
|||
|
|
return SummaryList.join(QLatin1Char('\n'));
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
QString BuildPnpSummary(
|
|||
|
|
const QVector<PnpIf>& pnpList,
|
|||
|
|
quint16 vendorId,
|
|||
|
|
quint16 productId)
|
|||
|
|
{
|
|||
|
|
QStringList lineList;
|
|||
|
|
for (const PnpIf& pnp : pnpList)
|
|||
|
|
{
|
|||
|
|
lineList.append(
|
|||
|
|
QStringLiteral("%1 -> %2")
|
|||
|
|
.arg(FormatAddressLabel(pnp.Address))
|
|||
|
|
.arg(FormatVidPidLabel(pnp.VendorId, pnp.ProductId)));
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (lineList.isEmpty())
|
|||
|
|
{
|
|||
|
|
return QStringLiteral(
|
|||
|
|
"PnP 扫描结果:未发现任何 DIS/PnP ID。");
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
QString summary = QStringLiteral(
|
|||
|
|
"PnP 扫描结果(目标 %1):\n%2")
|
|||
|
|
.arg(FormatVidPidLabel(vendorId, productId))
|
|||
|
|
.arg(lineList.join(QLatin1Char('\n')));
|
|||
|
|
return summary;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
QString BuildPnpProbeSummary(const QStringList& debugList)
|
|||
|
|
{
|
|||
|
|
if (debugList.isEmpty())
|
|||
|
|
{
|
|||
|
|
return QStringLiteral("PnP 探测日志:无。");
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return QStringLiteral("PnP 探测日志:\n%1").arg(debugList.join(QLatin1Char('\n')));
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
} // namespace
|
|||
|
|
|
|||
|
|
void Dri_Ble_Close(Dri_Ble_Struct_Port* port)
|
|||
|
|
{
|
|||
|
|
Dri_Ble_Struct_Context* ctx = port->p_Context;
|
|||
|
|
if (ctx != nullptr)
|
|||
|
|
{
|
|||
|
|
for (Dri_Ble_Struct_HidPort* hid : ctx->Hids) CloseHidPort(hid);
|
|||
|
|
for (Dri_Ble_Struct_ServiceContext* svc : ctx->Services) for (Dri_Ble_Struct_Subscription* sub : svc->Subs) { sub->Port = nullptr; if (sub->Event != nullptr) BluetoothGATTUnregisterEvent(sub->Event, BLUETOOTH_GATT_FLAG_NONE); }
|
|||
|
|
while (ctx->CallbackCount.load(std::memory_order_acquire) > 0) Sleep(1);
|
|||
|
|
for (Dri_Ble_Struct_ServiceContext* svc : ctx->Services)
|
|||
|
|
{
|
|||
|
|
for (Dri_Ble_Struct_Subscription* sub : svc->Subs) delete sub;
|
|||
|
|
if ((svc->Handle != nullptr) && (svc->Handle != INVALID_HANDLE_VALUE)) CloseHandle(svc->Handle);
|
|||
|
|
delete svc;
|
|||
|
|
}
|
|||
|
|
delete ctx;
|
|||
|
|
}
|
|||
|
|
*port = Dri_Ble_Struct_Port();
|
|||
|
|
}
|
|||
|
|
bool Dri_Ble_Init(Dri_Ble_Struct_Port* port, const Mid_Struct_DeviceConfig& deviceConfig, QString* textStatus)
|
|||
|
|
{
|
|||
|
|
Dri_Ble_Close(port);
|
|||
|
|
port->IsOpened = true;
|
|||
|
|
port->p_Context = new Dri_Ble_Struct_Context();
|
|||
|
|
|
|||
|
|
const QVector<DeviceIf> devices = EnumBleDevices();
|
|||
|
|
const QVector<HidIf> hids = EnumBleHids();
|
|||
|
|
const QVector<ServiceIf> services = EnumServices();
|
|||
|
|
QStringList pnpDebugList;
|
|||
|
|
const QVector<PnpIf> pnps = EnumPnps(devices, &pnpDebugList);
|
|||
|
|
const QStringList HidAddressList = CollectPnpAddresses(
|
|||
|
|
pnps,
|
|||
|
|
deviceConfig.VendorId,
|
|||
|
|
deviceConfig.ProductId);
|
|||
|
|
QString targetAddress;
|
|||
|
|
QString SelectStatus;
|
|||
|
|
if (HidAddressList.size() == 1)
|
|||
|
|
{
|
|||
|
|
targetAddress = HidAddressList.first();
|
|||
|
|
}
|
|||
|
|
else
|
|||
|
|
{
|
|||
|
|
SelectStatus = HidAddressList.isEmpty()
|
|||
|
|
? QStringLiteral("未检测到匹配 PnP ID %1 的 BLE 设备。")
|
|||
|
|
.arg(FormatVidPidLabel(deviceConfig.VendorId, deviceConfig.ProductId))
|
|||
|
|
: QStringLiteral("检测到多个匹配 PnP ID %1 的 BLE 设备,无法唯一绑定。")
|
|||
|
|
.arg(FormatVidPidLabel(deviceConfig.VendorId, deviceConfig.ProductId));
|
|||
|
|
}
|
|||
|
|
QString deviceLabel = targetAddress.isEmpty() ? QString() : DeviceLabel(targetAddress);
|
|||
|
|
|
|||
|
|
int charCount = 0;
|
|||
|
|
int subCount = 0;
|
|||
|
|
if (!targetAddress.isEmpty())
|
|||
|
|
{
|
|||
|
|
for (const ServiceIf& item : services)
|
|||
|
|
{
|
|||
|
|
if ((item.Uuid == kSvcCustom) && (ExtractAddress(item.InstanceId) == targetAddress)) AttachService(port, port->p_Context, item, deviceLabel, &charCount, &subCount);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (deviceLabel.isEmpty() && !targetAddress.isEmpty()) deviceLabel = DeviceLabel(targetAddress);
|
|||
|
|
|
|||
|
|
int hidCount = 0;
|
|||
|
|
if (!targetAddress.isEmpty())
|
|||
|
|
{
|
|||
|
|
for (const HidIf& hid : hids)
|
|||
|
|
{
|
|||
|
|
if ((ExtractAddress(hid.InstanceId) == targetAddress) && OpenHidPort(port->p_Context, hid, deviceLabel)) ++hidCount;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (deviceLabel.isEmpty() && !hids.isEmpty()) deviceLabel = QStringLiteral("BLE");
|
|||
|
|
|
|||
|
|
port->IsConnected = (charCount > 0) || (hidCount > 0);
|
|||
|
|
|
|||
|
|
|
|||
|
|
return port->IsConnected;
|
|||
|
|
}
|
|||
|
|
bool Dri_Ble_Read(Dri_Ble_Struct_Port* port, Mid_Struct_RawPacket* packet, QString*)
|
|||
|
|
{
|
|||
|
|
*packet = Mid_Struct_RawPacket();
|
|||
|
|
packet->PortName = QStringLiteral("Bluetooth");
|
|||
|
|
if (!port->IsOpened || (port->p_Context == nullptr)) return false;
|
|||
|
|
for (Dri_Ble_Struct_HidPort* hid : port->p_Context->Hids)
|
|||
|
|
{
|
|||
|
|
Mid_Struct_RawPacket hidPacket;
|
|||
|
|
if (Dri_Hid_Read(&hid->ReadPort, &hidPacket, nullptr))
|
|||
|
|
{
|
|||
|
|
QMutexLocker lock(&port->p_Context->Mutex);
|
|||
|
|
port->p_Context->Queue.append(hidPacket);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
QMutexLocker lock(&port->p_Context->Mutex);
|
|||
|
|
if (port->p_Context->Queue.isEmpty()) return false;
|
|||
|
|
*packet = port->p_Context->Queue.takeFirst();
|
|||
|
|
return packet->IsValid;
|
|||
|
|
}
|
|||
|
|
bool Dri_Ble_Write(Dri_Ble_Struct_Port* port, const QByteArray& bytes, QString* textStatus)
|
|||
|
|
{
|
|||
|
|
if (!port->IsOpened || (port->p_Context == nullptr)) { if (textStatus != nullptr) *textStatus = QStringLiteral("Bluetooth port is not open, packet send was skipped."); return false; }
|
|||
|
|
if (bytes.isEmpty()) { if (textStatus != nullptr) *textStatus = QStringLiteral("Bluetooth packet is empty."); return false; }
|
|||
|
|
const auto TryWrite = [&](auto MatchFunc, const QString& OkText)
|
|||
|
|
{
|
|||
|
|
for (Dri_Ble_Struct_HidPort* hid : port->p_Context->Hids)
|
|||
|
|
{
|
|||
|
|
if (!MatchFunc(hid->UsagePage, hid->Usage) || (hid->WriteHandle == INVALID_HANDLE_VALUE)) continue;
|
|||
|
|
if (Dri_Hid_WritePacket(hid->WriteHandle, hid->OutputLength, bytes, nullptr))
|
|||
|
|
{
|
|||
|
|
if (textStatus != nullptr) *textStatus = OkText;
|
|||
|
|
return true;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
return false;
|
|||
|
|
};
|
|||
|
|
if (TryWrite(IsCommandHid, QStringLiteral("Bluetooth Vendor command packet sent."))) return true;
|
|||
|
|
if (TryWrite(IsVendorHid, QStringLiteral("Bluetooth Vendor packet sent."))) return true;
|
|||
|
|
if (textStatus != nullptr) *textStatus = QStringLiteral("No writable Bluetooth Vendor endpoint was found.");
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
|