Push layered Qt host source files
This commit is contained in:
817
DRI/Dri_Ble.cpp
Normal file
817
DRI/Dri_Ble.cpp
Normal file
@@ -0,0 +1,817 @@
|
||||
#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;
|
||||
}
|
||||
|
||||
32
DRI/Dri_Ble.h
Normal file
32
DRI/Dri_Ble.h
Normal file
@@ -0,0 +1,32 @@
|
||||
#pragma once
|
||||
|
||||
#include "MID/Mid_Def.h"
|
||||
#include <QtCore/QString>
|
||||
|
||||
struct Dri_Ble_Struct_Context;
|
||||
|
||||
// BLE port combines two channels:
|
||||
// 1. custom GATT notifications
|
||||
// 2. BLE HID reports and writes
|
||||
struct Dri_Ble_Struct_Port
|
||||
{
|
||||
bool IsOpened = false;
|
||||
bool IsConnected = false;
|
||||
QString TextEndpointSummary;
|
||||
Dri_Ble_Struct_Context* p_Context = nullptr;
|
||||
};
|
||||
|
||||
void Dri_Ble_Close(Dri_Ble_Struct_Port* p_Port);
|
||||
bool Dri_Ble_Init(
|
||||
Dri_Ble_Struct_Port* p_Port,
|
||||
const Mid_Struct_DeviceConfig& DeviceConfig,
|
||||
QString* p_TextStatus);
|
||||
bool Dri_Ble_Read(
|
||||
Dri_Ble_Struct_Port* p_Port,
|
||||
Mid_Struct_RawPacket* p_Packet,
|
||||
QString* p_TextStatus);
|
||||
bool Dri_Ble_Write(
|
||||
Dri_Ble_Struct_Port* p_Port,
|
||||
const QByteArray& ByteArray,
|
||||
QString* p_TextStatus);
|
||||
|
||||
623
DRI/Dri_Cdc.cpp
Normal file
623
DRI/Dri_Cdc.cpp
Normal file
@@ -0,0 +1,623 @@
|
||||
#include "DRI/Dri_Cdc.h"
|
||||
|
||||
#include "COM/Com_Protocol.h"
|
||||
#include <QtCore/QDir>
|
||||
#include <QtCore/QQueue>
|
||||
#include <QtCore/QStringList>
|
||||
#include <QtCore/QVector>
|
||||
#include <QtSerialPort/QSerialPort>
|
||||
#include <QtSerialPort/QSerialPortInfo>
|
||||
|
||||
struct Dri_Cdc_Struct_Candidate
|
||||
{
|
||||
QSerialPort* p_SerialPort = nullptr;
|
||||
QString PortName;
|
||||
QString EndpointId;
|
||||
QString SystemLocation;
|
||||
QByteArray PendingBytes;
|
||||
QQueue<Com_Struct_RawPacket> PacketQueue;
|
||||
};
|
||||
|
||||
struct Dri_Cdc_Struct_Context
|
||||
{
|
||||
QVector<Dri_Cdc_Struct_Candidate> CandidateList;
|
||||
int ActiveCandidateIndex = -1;
|
||||
};
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
QString Dri_Cdc_FormatPortLabel(const QSerialPortInfo& PortInfo)
|
||||
{
|
||||
const QString Description = PortInfo.description().trimmed();
|
||||
return Description.isEmpty()
|
||||
? QStringLiteral("USB CDC (%1)").arg(PortInfo.portName())
|
||||
: QStringLiteral("USB CDC (%1 - %2)").arg(PortInfo.portName(), Description);
|
||||
}
|
||||
|
||||
QString Dri_Cdc_BuildEndpointId(const QSerialPortInfo& PortInfo)
|
||||
{
|
||||
const QString SystemLocation = QDir::cleanPath(PortInfo.systemLocation());
|
||||
if (!SystemLocation.isEmpty())
|
||||
{
|
||||
return SystemLocation;
|
||||
}
|
||||
|
||||
return PortInfo.portName().trimmed();
|
||||
}
|
||||
|
||||
bool Dri_Cdc_IsPortStillPresent(const Dri_Cdc_Struct_Candidate& Candidate)
|
||||
{
|
||||
for (const QSerialPortInfo& PortInfo : QSerialPortInfo::availablePorts())
|
||||
{
|
||||
if (!Candidate.SystemLocation.isEmpty() &&
|
||||
(QDir::cleanPath(PortInfo.systemLocation())
|
||||
.compare(Candidate.SystemLocation, Qt::CaseInsensitive) == 0))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void Dri_Cdc_DeleteCandidate(Dri_Cdc_Struct_Candidate* p_Candidate)
|
||||
{
|
||||
if (p_Candidate == nullptr)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if ((p_Candidate->p_SerialPort != nullptr) && p_Candidate->p_SerialPort->isOpen())
|
||||
{
|
||||
p_Candidate->p_SerialPort->setDataTerminalReady(false);
|
||||
p_Candidate->p_SerialPort->close();
|
||||
}
|
||||
|
||||
delete p_Candidate->p_SerialPort;
|
||||
p_Candidate->p_SerialPort = nullptr;
|
||||
p_Candidate->PendingBytes.clear();
|
||||
p_Candidate->PacketQueue.clear();
|
||||
}
|
||||
|
||||
void Dri_Cdc_DeleteContext(Dri_Cdc_Struct_Context* p_Context)
|
||||
{
|
||||
if (p_Context == nullptr)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
for (int Index = 0; Index < p_Context->CandidateList.size(); ++Index)
|
||||
{
|
||||
Dri_Cdc_DeleteCandidate(&p_Context->CandidateList[Index]);
|
||||
}
|
||||
|
||||
delete p_Context;
|
||||
}
|
||||
|
||||
int Dri_Cdc_FindCandidateIndex(
|
||||
const Dri_Cdc_Struct_Context* p_Context,
|
||||
const QString& EndpointId)
|
||||
{
|
||||
if ((p_Context == nullptr) || EndpointId.trimmed().isEmpty())
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
for (int Index = 0; Index < p_Context->CandidateList.size(); ++Index)
|
||||
{
|
||||
const Dri_Cdc_Struct_Candidate& Candidate = p_Context->CandidateList.at(Index);
|
||||
if (Candidate.EndpointId.compare(EndpointId, Qt::CaseInsensitive) == 0)
|
||||
{
|
||||
return Index;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
void Dri_Cdc_RefreshPortSummary(
|
||||
Dri_Cdc_Struct_Port* p_Port,
|
||||
Dri_Cdc_Struct_Context* p_Context)
|
||||
{
|
||||
if ((p_Port == nullptr) || (p_Context == nullptr))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if ((p_Context->ActiveCandidateIndex >= 0) &&
|
||||
((p_Context->ActiveCandidateIndex >= p_Context->CandidateList.size()) ||
|
||||
(p_Context->CandidateList.at(p_Context->ActiveCandidateIndex).p_SerialPort == nullptr) ||
|
||||
!p_Context->CandidateList.at(p_Context->ActiveCandidateIndex).p_SerialPort->isOpen()))
|
||||
{
|
||||
p_Context->ActiveCandidateIndex = -1;
|
||||
}
|
||||
|
||||
QStringList OpenedPortList;
|
||||
for (const Dri_Cdc_Struct_Candidate& Candidate : p_Context->CandidateList)
|
||||
{
|
||||
if ((Candidate.p_SerialPort != nullptr) && Candidate.p_SerialPort->isOpen())
|
||||
{
|
||||
OpenedPortList.append(Candidate.PortName);
|
||||
}
|
||||
}
|
||||
|
||||
if (OpenedPortList.isEmpty())
|
||||
{
|
||||
p_Port->IsOpened = false;
|
||||
p_Port->PortName.clear();
|
||||
return;
|
||||
}
|
||||
|
||||
p_Port->IsOpened = true;
|
||||
if (p_Context->ActiveCandidateIndex >= 0)
|
||||
{
|
||||
p_Port->PortName =
|
||||
p_Context->CandidateList.at(p_Context->ActiveCandidateIndex).PortName;
|
||||
return;
|
||||
}
|
||||
|
||||
p_Port->PortName = OpenedPortList.size() == 1
|
||||
? OpenedPortList.first()
|
||||
: QStringLiteral("USB CDC candidates (%1)").arg(OpenedPortList.join(QStringLiteral(", ")));
|
||||
}
|
||||
|
||||
bool Dri_Cdc_WritePacket(
|
||||
Dri_Cdc_Struct_Candidate* p_Candidate,
|
||||
const QByteArray& PacketBody)
|
||||
{
|
||||
if ((p_Candidate == nullptr) ||
|
||||
(p_Candidate->p_SerialPort == nullptr) ||
|
||||
!p_Candidate->p_SerialPort->isOpen())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const qint64 BytesWritten = p_Candidate->p_SerialPort->write(PacketBody);
|
||||
if (BytesWritten != PacketBody.size())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
p_Candidate->p_SerialPort->flush();
|
||||
return p_Candidate->p_SerialPort->waitForBytesWritten(100);
|
||||
}
|
||||
|
||||
void Dri_Cdc_EnqueuePacket(
|
||||
Dri_Cdc_Struct_Candidate* p_Candidate,
|
||||
const QByteArray& PacketBody,
|
||||
Com_Enum_ProtocolType Type)
|
||||
{
|
||||
if ((p_Candidate == nullptr) || PacketBody.isEmpty() ||
|
||||
(Type == Com_Enum_ProtocolType_None))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Com_Struct_RawPacket Packet;
|
||||
Packet.IsValid = true;
|
||||
Packet.Source = Com_Enum_RawPacketSource_UsbCdc;
|
||||
Packet.ProtocolType = Type;
|
||||
Packet.ByteArray = PacketBody;
|
||||
Packet.PortName = p_Candidate->PortName;
|
||||
Packet.EndpointId = p_Candidate->EndpointId;
|
||||
p_Candidate->PacketQueue.enqueue(Packet);
|
||||
}
|
||||
|
||||
void Dri_Cdc_BufferIncomingBytes(
|
||||
Dri_Cdc_Struct_Candidate* p_Candidate,
|
||||
const QByteArray& IncomingBytes)
|
||||
{
|
||||
if ((p_Candidate == nullptr) || IncomingBytes.isEmpty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
p_Candidate->PendingBytes.append(IncomingBytes);
|
||||
|
||||
while (true)
|
||||
{
|
||||
QByteArray PacketBody;
|
||||
Com_Enum_ProtocolType Type = Com_Enum_ProtocolType_None;
|
||||
if (!Com_Protocol_TryTakePacket(&p_Candidate->PendingBytes, &PacketBody, &Type))
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
Dri_Cdc_EnqueuePacket(p_Candidate, PacketBody, Type);
|
||||
}
|
||||
}
|
||||
|
||||
bool Dri_Cdc_DequeuePacket(
|
||||
Dri_Cdc_Struct_Candidate* p_Candidate,
|
||||
Com_Struct_RawPacket* p_Packet,
|
||||
QString* p_TextStatus)
|
||||
{
|
||||
if ((p_Candidate == nullptr) || (p_Packet == nullptr) || p_Candidate->PacketQueue.isEmpty())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
*p_Packet = p_Candidate->PacketQueue.dequeue();
|
||||
if (p_TextStatus != nullptr)
|
||||
{
|
||||
*p_TextStatus =
|
||||
QStringLiteral("%1 RX %2")
|
||||
.arg(
|
||||
p_Candidate->PortName,
|
||||
QString::fromLatin1(p_Packet->ByteArray.toHex(' ').toUpper()));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void Dri_Cdc_Close(Dri_Cdc_Struct_Port* p_Port)
|
||||
{
|
||||
Dri_Cdc_DeleteContext(p_Port->p_Context);
|
||||
*p_Port = Dri_Cdc_Struct_Port();
|
||||
}
|
||||
|
||||
bool Dri_Cdc_Init(
|
||||
Dri_Cdc_Struct_Port* p_Port,
|
||||
const Com_Struct_DeviceConfig& DeviceConfig,
|
||||
QString* p_TextStatus)
|
||||
{
|
||||
Dri_Cdc_Close(p_Port);
|
||||
|
||||
const QVector<QSerialPortInfo> PortInfoList = QSerialPortInfo::availablePorts().toVector();
|
||||
if (PortInfoList.isEmpty())
|
||||
{
|
||||
if (p_TextStatus != nullptr)
|
||||
{
|
||||
*p_TextStatus = QStringLiteral("No USB CDC port is available.");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
auto* p_Context = new Dri_Cdc_Struct_Context();
|
||||
QStringList ErrorList;
|
||||
for (const QSerialPortInfo& PortInfo : PortInfoList)
|
||||
{
|
||||
Dri_Cdc_Struct_Candidate Candidate;
|
||||
Candidate.p_SerialPort = new QSerialPort(PortInfo);
|
||||
Candidate.PortName = Dri_Cdc_FormatPortLabel(PortInfo);
|
||||
Candidate.EndpointId = Dri_Cdc_BuildEndpointId(PortInfo);
|
||||
Candidate.SystemLocation = QDir::cleanPath(PortInfo.systemLocation());
|
||||
|
||||
Candidate.p_SerialPort->setBaudRate(QSerialPort::Baud115200);
|
||||
Candidate.p_SerialPort->setDataBits(QSerialPort::Data8);
|
||||
Candidate.p_SerialPort->setParity(QSerialPort::NoParity);
|
||||
Candidate.p_SerialPort->setStopBits(QSerialPort::OneStop);
|
||||
Candidate.p_SerialPort->setFlowControl(QSerialPort::NoFlowControl);
|
||||
|
||||
if (!Candidate.p_SerialPort->open(QIODevice::ReadWrite))
|
||||
{
|
||||
ErrorList.append(
|
||||
QStringLiteral("%1: %2")
|
||||
.arg(PortInfo.portName(), Candidate.p_SerialPort->errorString()));
|
||||
Dri_Cdc_DeleteCandidate(&Candidate);
|
||||
continue;
|
||||
}
|
||||
|
||||
Candidate.p_SerialPort->setDataTerminalReady(true);
|
||||
p_Context->CandidateList.append(Candidate);
|
||||
}
|
||||
|
||||
if (p_Context->CandidateList.isEmpty())
|
||||
{
|
||||
if (p_TextStatus != nullptr)
|
||||
{
|
||||
*p_TextStatus = ErrorList.isEmpty()
|
||||
? QStringLiteral("USB CDC ports were found, but all opens failed.")
|
||||
: QStringLiteral("USB CDC open failed: %1").arg(ErrorList.join(QStringLiteral(" | ")));
|
||||
}
|
||||
Dri_Cdc_DeleteContext(p_Context);
|
||||
return false;
|
||||
}
|
||||
|
||||
p_Port->p_Context = p_Context;
|
||||
Dri_Cdc_RefreshPortSummary(p_Port, p_Context);
|
||||
Q_UNUSED(DeviceConfig);
|
||||
if (p_TextStatus != nullptr)
|
||||
{
|
||||
*p_TextStatus = QStringLiteral("%1 connected.").arg(p_Port->PortName);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Dri_Cdc_Read(
|
||||
Dri_Cdc_Struct_Port* p_Port,
|
||||
Com_Struct_RawPacket* p_Packet,
|
||||
QString* p_TextStatus)
|
||||
{
|
||||
*p_Packet = Com_Struct_RawPacket();
|
||||
p_Packet->Source = Com_Enum_RawPacketSource_UsbCdc;
|
||||
p_Packet->PortName = p_Port->PortName;
|
||||
|
||||
Dri_Cdc_Struct_Context* p_Context = p_Port->p_Context;
|
||||
if (!p_Port->IsOpened || (p_Context == nullptr) || p_Context->CandidateList.isEmpty())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const int StartIndex =
|
||||
p_Context->ActiveCandidateIndex >= 0 ? p_Context->ActiveCandidateIndex : 0;
|
||||
const int EndIndex =
|
||||
p_Context->ActiveCandidateIndex >= 0
|
||||
? (p_Context->ActiveCandidateIndex + 1)
|
||||
: p_Context->CandidateList.size();
|
||||
|
||||
for (int Index = StartIndex; Index < EndIndex; ++Index)
|
||||
{
|
||||
Dri_Cdc_Struct_Candidate& Candidate = p_Context->CandidateList[Index];
|
||||
if ((Candidate.p_SerialPort == nullptr) || !Candidate.p_SerialPort->isOpen())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!Dri_Cdc_IsPortStillPresent(Candidate))
|
||||
{
|
||||
if (p_TextStatus != nullptr)
|
||||
{
|
||||
*p_TextStatus = QStringLiteral("%1 disconnected.").arg(Candidate.PortName);
|
||||
}
|
||||
|
||||
Dri_Cdc_DeleteCandidate(&Candidate);
|
||||
if (p_Context->ActiveCandidateIndex == Index)
|
||||
{
|
||||
Dri_Cdc_Close(p_Port);
|
||||
return false;
|
||||
}
|
||||
|
||||
Dri_Cdc_RefreshPortSummary(p_Port, p_Context);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (Dri_Cdc_DequeuePacket(&Candidate, p_Packet, p_TextStatus))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
const QByteArray IncomingBytes = Candidate.p_SerialPort->readAll();
|
||||
if (IncomingBytes.isEmpty())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
Dri_Cdc_BufferIncomingBytes(&Candidate, IncomingBytes);
|
||||
if (Dri_Cdc_DequeuePacket(&Candidate, p_Packet, p_TextStatus))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if ((p_TextStatus != nullptr) && !Candidate.PendingBytes.isEmpty())
|
||||
{
|
||||
*p_TextStatus =
|
||||
QStringLiteral("%1 has pending protobuf bytes and is waiting for the next CDC chunk.")
|
||||
.arg(Candidate.PortName);
|
||||
}
|
||||
}
|
||||
|
||||
Dri_Cdc_RefreshPortSummary(p_Port, p_Context);
|
||||
if (!p_Port->IsOpened)
|
||||
{
|
||||
Dri_Cdc_Close(p_Port);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Dri_Cdc_LockCandidate(
|
||||
Dri_Cdc_Struct_Port* p_Port,
|
||||
const QString& EndpointId,
|
||||
QString* p_TextStatus)
|
||||
{
|
||||
Dri_Cdc_Struct_Context* p_Context = p_Port->p_Context;
|
||||
if (!p_Port->IsOpened || (p_Context == nullptr))
|
||||
{
|
||||
if (p_TextStatus != nullptr)
|
||||
{
|
||||
*p_TextStatus = QStringLiteral("USB CDC is not open, so the candidate cannot be locked.");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
const int ActiveIndex = Dri_Cdc_FindCandidateIndex(p_Context, EndpointId);
|
||||
if (ActiveIndex < 0)
|
||||
{
|
||||
if (p_TextStatus != nullptr)
|
||||
{
|
||||
*p_TextStatus = QStringLiteral("The target USB CDC candidate was not found.");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if ((p_Context->CandidateList.at(ActiveIndex).p_SerialPort == nullptr) ||
|
||||
!p_Context->CandidateList.at(ActiveIndex).p_SerialPort->isOpen())
|
||||
{
|
||||
if (p_TextStatus != nullptr)
|
||||
{
|
||||
*p_TextStatus = QStringLiteral("The target USB CDC candidate is already closed.");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (p_Context->ActiveCandidateIndex == ActiveIndex)
|
||||
{
|
||||
if (p_TextStatus != nullptr)
|
||||
{
|
||||
*p_TextStatus =
|
||||
QStringLiteral("USB CDC already locked to %1.")
|
||||
.arg(p_Context->CandidateList.at(ActiveIndex).PortName);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
for (int Index = 0; Index < p_Context->CandidateList.size(); ++Index)
|
||||
{
|
||||
if (Index == ActiveIndex)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
Dri_Cdc_DeleteCandidate(&p_Context->CandidateList[Index]);
|
||||
}
|
||||
|
||||
p_Context->ActiveCandidateIndex = ActiveIndex;
|
||||
Dri_Cdc_RefreshPortSummary(p_Port, p_Context);
|
||||
if (p_TextStatus != nullptr)
|
||||
{
|
||||
*p_TextStatus = QStringLiteral("USB CDC locked to %1.").arg(p_Port->PortName);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Dri_Cdc_DiscardCandidate(
|
||||
Dri_Cdc_Struct_Port* p_Port,
|
||||
const QString& EndpointId,
|
||||
QString* p_TextStatus)
|
||||
{
|
||||
Dri_Cdc_Struct_Context* p_Context = p_Port->p_Context;
|
||||
if (!p_Port->IsOpened || (p_Context == nullptr))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const int CandidateIndex = Dri_Cdc_FindCandidateIndex(p_Context, EndpointId);
|
||||
if (CandidateIndex < 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const QString PortLabel = p_Context->CandidateList.at(CandidateIndex).PortName;
|
||||
const bool IsActiveCandidate = p_Context->ActiveCandidateIndex == CandidateIndex;
|
||||
Dri_Cdc_DeleteCandidate(&p_Context->CandidateList[CandidateIndex]);
|
||||
|
||||
if (IsActiveCandidate)
|
||||
{
|
||||
Dri_Cdc_Close(p_Port);
|
||||
if (p_TextStatus != nullptr)
|
||||
{
|
||||
*p_TextStatus = QStringLiteral("Discarded the locked USB CDC candidate %1.").arg(PortLabel);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
Dri_Cdc_RefreshPortSummary(p_Port, p_Context);
|
||||
if (!p_Port->IsOpened)
|
||||
{
|
||||
Dri_Cdc_Close(p_Port);
|
||||
if (p_TextStatus != nullptr)
|
||||
{
|
||||
*p_TextStatus = QStringLiteral("All USB CDC candidates were discarded.");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
if (p_TextStatus != nullptr)
|
||||
{
|
||||
*p_TextStatus = QStringLiteral("Discarded USB CDC candidate %1.").arg(PortLabel);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Dri_Cdc_Write(
|
||||
Dri_Cdc_Struct_Port* p_Port,
|
||||
const QByteArray& PacketBody,
|
||||
QString* p_TextStatus)
|
||||
{
|
||||
Dri_Cdc_Struct_Context* p_Context = p_Port->p_Context;
|
||||
if (!p_Port->IsOpened || (p_Context == nullptr) || p_Context->CandidateList.isEmpty())
|
||||
{
|
||||
if (p_TextStatus != nullptr)
|
||||
{
|
||||
*p_TextStatus = QStringLiteral("USB CDC is not open. Skip send.");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (PacketBody.isEmpty())
|
||||
{
|
||||
if (p_TextStatus != nullptr)
|
||||
{
|
||||
*p_TextStatus = QStringLiteral("USB CDC packet body is empty.");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (p_Context->ActiveCandidateIndex >= 0)
|
||||
{
|
||||
Dri_Cdc_Struct_Candidate& ActiveCandidate =
|
||||
p_Context->CandidateList[p_Context->ActiveCandidateIndex];
|
||||
if (!Dri_Cdc_WritePacket(&ActiveCandidate, PacketBody))
|
||||
{
|
||||
if (p_TextStatus != nullptr)
|
||||
{
|
||||
*p_TextStatus =
|
||||
QStringLiteral("%1 鍐欏叆澶辫触锛?2")
|
||||
.arg(
|
||||
ActiveCandidate.PortName,
|
||||
ActiveCandidate.p_SerialPort == nullptr
|
||||
? QStringLiteral("port already closed")
|
||||
: ActiveCandidate.p_SerialPort->errorString());
|
||||
}
|
||||
|
||||
Dri_Cdc_Close(p_Port);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (p_TextStatus != nullptr)
|
||||
{
|
||||
*p_TextStatus =
|
||||
QStringLiteral("%1 TX %2")
|
||||
.arg(
|
||||
ActiveCandidate.PortName,
|
||||
QString::fromLatin1(PacketBody.toHex(' ').toUpper()));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
int SuccessCount = 0;
|
||||
QStringList SuccessPortList;
|
||||
QStringList ErrorList;
|
||||
for (Dri_Cdc_Struct_Candidate& Candidate : p_Context->CandidateList)
|
||||
{
|
||||
if ((Candidate.p_SerialPort == nullptr) || !Candidate.p_SerialPort->isOpen())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (Dri_Cdc_WritePacket(&Candidate, PacketBody))
|
||||
{
|
||||
++SuccessCount;
|
||||
SuccessPortList.append(Candidate.PortName);
|
||||
continue;
|
||||
}
|
||||
|
||||
ErrorList.append(
|
||||
QStringLiteral("%1: %2")
|
||||
.arg(Candidate.PortName, Candidate.p_SerialPort->errorString()));
|
||||
}
|
||||
|
||||
if (SuccessCount > 0)
|
||||
{
|
||||
if (p_TextStatus != nullptr)
|
||||
{
|
||||
*p_TextStatus =
|
||||
QStringLiteral("USB CDC 骞挎挱 TX %1锛岀洰鏍囷細%2")
|
||||
.arg(
|
||||
QString::fromLatin1(PacketBody.toHex(' ').toUpper()),
|
||||
SuccessPortList.join(QStringLiteral(", ")));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
if (p_TextStatus != nullptr)
|
||||
{
|
||||
*p_TextStatus = ErrorList.isEmpty()
|
||||
? QStringLiteral("USB CDC send failed.")
|
||||
: QStringLiteral("USB CDC 鍙戦€佸け璐ワ細%1").arg(ErrorList.join(QStringLiteral(" | ")));
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
40
DRI/Dri_Cdc.h
Normal file
40
DRI/Dri_Cdc.h
Normal file
@@ -0,0 +1,40 @@
|
||||
#pragma once
|
||||
|
||||
#include "COM/Com_Def.h"
|
||||
#include <QtCore/QString>
|
||||
|
||||
struct Dri_Cdc_Struct_Context;
|
||||
|
||||
struct Dri_Cdc_Struct_Port
|
||||
{
|
||||
Dri_Cdc_Struct_Context* p_Context = nullptr;
|
||||
bool IsOpened = false;
|
||||
QString PortName;
|
||||
};
|
||||
|
||||
void Dri_Cdc_Close(Dri_Cdc_Struct_Port* p_Port);
|
||||
bool Dri_Cdc_Init(
|
||||
Dri_Cdc_Struct_Port* p_Port,
|
||||
const Com_Struct_DeviceConfig& DeviceConfig,
|
||||
QString* p_TextStatus);
|
||||
bool Dri_Cdc_Read(
|
||||
Dri_Cdc_Struct_Port* p_Port,
|
||||
Com_Struct_RawPacket* p_Packet,
|
||||
QString* p_TextStatus);
|
||||
|
||||
// Lock the confirmed CDC candidate after LOGIC accepts a HelloRsp.
|
||||
bool Dri_Cdc_LockCandidate(
|
||||
Dri_Cdc_Struct_Port* p_Port,
|
||||
const QString& EndpointId,
|
||||
QString* p_TextStatus);
|
||||
|
||||
// Drop one mismatched CDC candidate without tearing down the whole CDC scan set.
|
||||
bool Dri_Cdc_DiscardCandidate(
|
||||
Dri_Cdc_Struct_Port* p_Port,
|
||||
const QString& EndpointId,
|
||||
QString* p_TextStatus);
|
||||
|
||||
bool Dri_Cdc_Write(
|
||||
Dri_Cdc_Struct_Port* p_Port,
|
||||
const QByteArray& PacketBody,
|
||||
QString* p_TextStatus);
|
||||
@@ -1,168 +1,52 @@
|
||||
#include "DRI/Dri_Consumer.h"
|
||||
#include "DRI/Dri_Consumer.h"
|
||||
|
||||
namespace
|
||||
void Dri_Consumer_Close(Dri_Consumer_Struct_Port* p_Port)
|
||||
{
|
||||
|
||||
bool Dri_Consumer_Func_BeginRead(Dri_Consumer_Struct_Port* p_Port, QString* p_TextStatus)
|
||||
{
|
||||
if (!p_Port->IsOpened || (p_Port->InputLength == 0))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (p_Port->IsReadPending)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
ResetEvent(p_Port->HandleEvent);
|
||||
p_Port->ReadBuffer.fill(0, p_Port->InputLength);
|
||||
p_Port->OverlappedRead = {};
|
||||
p_Port->OverlappedRead.hEvent = p_Port->HandleEvent;
|
||||
|
||||
DWORD BytesRead = 0;
|
||||
if (ReadFile(
|
||||
p_Port->HandleRead,
|
||||
p_Port->ReadBuffer.data(),
|
||||
p_Port->InputLength,
|
||||
&BytesRead,
|
||||
&p_Port->OverlappedRead) ||
|
||||
(GetLastError() == ERROR_IO_PENDING))
|
||||
{
|
||||
p_Port->IsReadPending = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (p_TextStatus != nullptr)
|
||||
{
|
||||
*p_TextStatus = QStringLiteral("Consumer 接口启动异步读取失败:%1").arg(GetLastError());
|
||||
}
|
||||
return false;
|
||||
Dri_Hid_CloseReadPort(&p_Port->ReadPort);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void Dri_Consumer_Func_Close(Dri_Consumer_Struct_Port* p_Port)
|
||||
{
|
||||
if ((p_Port->HandleRead != INVALID_HANDLE_VALUE) && p_Port->IsReadPending)
|
||||
{
|
||||
CancelIoEx(p_Port->HandleRead, &p_Port->OverlappedRead);
|
||||
}
|
||||
if (p_Port->HandleRead != INVALID_HANDLE_VALUE)
|
||||
{
|
||||
CloseHandle(p_Port->HandleRead);
|
||||
}
|
||||
if (p_Port->HandleEvent != nullptr)
|
||||
{
|
||||
CloseHandle(p_Port->HandleEvent);
|
||||
}
|
||||
|
||||
*p_Port = Dri_Consumer_Struct_Port();
|
||||
}
|
||||
|
||||
bool Dri_Consumer_Func_Open(
|
||||
bool Dri_Consumer_Init(
|
||||
Dri_Consumer_Struct_Port* p_Port,
|
||||
const Mid_Struct_DeviceConfig& DeviceConfig,
|
||||
QString* p_TextStatus)
|
||||
{
|
||||
Dri_Consumer_Func_Close(p_Port);
|
||||
Dri_Consumer_Close(p_Port);
|
||||
|
||||
Mid_Struct_DeviceMatch Match;
|
||||
Match.VendorId = DeviceConfig.VendorId;
|
||||
Match.ProductId = DeviceConfig.ProductId;
|
||||
Match.UsagePage = MID_CONST_USAGE_PAGE_CONSUMER;
|
||||
Match.Usage = MID_CONST_USAGE_CONSUMER;
|
||||
|
||||
QString DevicePath;
|
||||
quint16 InputLength = 0;
|
||||
if (!Mid_Func_FindHidInterface(
|
||||
Mid_Func_GetConsumerMatch(DeviceConfig),
|
||||
if (!Mid_FindHidInterface(
|
||||
Match,
|
||||
&DevicePath,
|
||||
&InputLength,
|
||||
nullptr))
|
||||
{
|
||||
if (p_TextStatus != nullptr)
|
||||
{
|
||||
*p_TextStatus = QStringLiteral("未找到 Consumer 接口:000C / 0001。");
|
||||
*p_TextStatus = QStringLiteral("Consumer interface was not found: 000C / 0001.");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
p_Port->HandleRead = CreateFileW(
|
||||
reinterpret_cast<LPCWSTR>(DevicePath.utf16()),
|
||||
GENERIC_READ,
|
||||
FILE_SHARE_READ | FILE_SHARE_WRITE,
|
||||
nullptr,
|
||||
OPEN_EXISTING,
|
||||
FILE_FLAG_OVERLAPPED,
|
||||
nullptr);
|
||||
if (p_Port->HandleRead == INVALID_HANDLE_VALUE)
|
||||
{
|
||||
if (p_TextStatus != nullptr)
|
||||
{
|
||||
*p_TextStatus = QStringLiteral("Consumer 接口打开失败:%1").arg(GetLastError());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
p_Port->HandleEvent = CreateEventW(nullptr, TRUE, FALSE, nullptr);
|
||||
if (p_Port->HandleEvent == nullptr)
|
||||
{
|
||||
if (p_TextStatus != nullptr)
|
||||
{
|
||||
*p_TextStatus = QStringLiteral("Consumer 接口创建事件失败:%1").arg(GetLastError());
|
||||
}
|
||||
Dri_Consumer_Func_Close(p_Port);
|
||||
return false;
|
||||
}
|
||||
|
||||
p_Port->InputLength = InputLength;
|
||||
p_Port->ReadBuffer = QByteArray(InputLength, 0);
|
||||
p_Port->OverlappedRead.hEvent = p_Port->HandleEvent;
|
||||
p_Port->IsOpened = true;
|
||||
|
||||
Dri_Consumer_Func_BeginRead(p_Port, p_TextStatus);
|
||||
return true;
|
||||
return Dri_Hid_InitReadPort(
|
||||
&p_Port->ReadPort,
|
||||
DevicePath,
|
||||
InputLength,
|
||||
Mid_Enum_RawPacketSource_UsbConsumerHid,
|
||||
QStringLiteral("Consumer"),
|
||||
p_TextStatus);
|
||||
}
|
||||
|
||||
bool Dri_Consumer_Func_Read(
|
||||
bool Dri_Consumer_Read(
|
||||
Dri_Consumer_Struct_Port* p_Port,
|
||||
Mid_Struct_RawPacket* p_Packet,
|
||||
QString* p_TextStatus)
|
||||
{
|
||||
p_Packet->IsValid = false;
|
||||
p_Packet->ByteArray.clear();
|
||||
p_Packet->PortName = QStringLiteral("Consumer");
|
||||
|
||||
if (!p_Port->IsOpened)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!p_Port->IsReadPending)
|
||||
{
|
||||
Dri_Consumer_Func_BeginRead(p_Port, p_TextStatus);
|
||||
return false;
|
||||
}
|
||||
|
||||
DWORD BytesRead = 0;
|
||||
if (!GetOverlappedResult(p_Port->HandleRead, &p_Port->OverlappedRead, &BytesRead, FALSE))
|
||||
{
|
||||
const DWORD ErrorCode = GetLastError();
|
||||
if (ErrorCode == ERROR_IO_INCOMPLETE)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (p_TextStatus != nullptr)
|
||||
{
|
||||
*p_TextStatus = QStringLiteral("Consumer 接口读包失败:%1").arg(ErrorCode);
|
||||
}
|
||||
Dri_Consumer_Func_Close(p_Port);
|
||||
return false;
|
||||
}
|
||||
|
||||
p_Port->IsReadPending = false;
|
||||
if (BytesRead > 0)
|
||||
{
|
||||
p_Packet->IsValid = true;
|
||||
p_Packet->ByteArray = p_Port->ReadBuffer.left(static_cast<int>(BytesRead));
|
||||
}
|
||||
|
||||
Dri_Consumer_Func_BeginRead(p_Port, p_TextStatus);
|
||||
return p_Packet->IsValid;
|
||||
return Dri_Hid_Read(&p_Port->ReadPort, p_Packet, p_TextStatus);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,35 +1,20 @@
|
||||
#pragma once
|
||||
#pragma once
|
||||
|
||||
#include "MID/Mid_Def.h"
|
||||
#include <QtCore/QByteArray>
|
||||
#include <QtCore/QString>
|
||||
#include <Windows.h>
|
||||
#include "DRI/Dri_Hid.h"
|
||||
|
||||
/*
|
||||
* DRI Consumer 层:抽象 HID Consumer 端点,封装 Win32 句柄、缓存与状态。
|
||||
*
|
||||
*/
|
||||
// USB consumer report reader.
|
||||
struct Dri_Consumer_Struct_Port
|
||||
{
|
||||
/* 设备读写所需的句柄与异步上下文 */
|
||||
HANDLE HandleRead = INVALID_HANDLE_VALUE;
|
||||
HANDLE HandleEvent = nullptr;
|
||||
OVERLAPPED OverlappedRead = {};
|
||||
/* 快速判断是否已经建立连接以及当前是否正挂起读 */
|
||||
bool IsOpened = false;
|
||||
bool IsReadPending = false;
|
||||
/* USB 输入报文长度 & 环形缓存 */
|
||||
quint16 InputLength = 0;
|
||||
QByteArray ReadBuffer;
|
||||
Dri_Hid_Struct_ReadPort ReadPort;
|
||||
};
|
||||
|
||||
/* 主动释放 Consumer 端口,确保句柄/Overlapped 都被 reset。 */
|
||||
void Dri_Consumer_Func_Close(Dri_Consumer_Struct_Port* p_Port);
|
||||
/* 打开设备:按照 VID/PID 查询接口并创建 Overlapped 句柄。 */
|
||||
bool Dri_Consumer_Func_Open(Dri_Consumer_Struct_Port* p_Port,
|
||||
void Dri_Consumer_Close(Dri_Consumer_Struct_Port* p_Port);
|
||||
bool Dri_Consumer_Init(
|
||||
Dri_Consumer_Struct_Port* p_Port,
|
||||
const Mid_Struct_DeviceConfig& DeviceConfig,
|
||||
QString* p_TextStatus);
|
||||
/* 读取最新的 Consumer 原始包,失败时返回状态描述。 */
|
||||
bool Dri_Consumer_Func_Read(Dri_Consumer_Struct_Port* p_Port,
|
||||
bool Dri_Consumer_Read(
|
||||
Dri_Consumer_Struct_Port* p_Port,
|
||||
Mid_Struct_RawPacket* p_Packet,
|
||||
QString* p_TextStatus);
|
||||
|
||||
|
||||
189
DRI/Dri_Hid.cpp
Normal file
189
DRI/Dri_Hid.cpp
Normal file
@@ -0,0 +1,189 @@
|
||||
#include "DRI/Dri_Hid.h"
|
||||
|
||||
#include <hidsdi.h>
|
||||
|
||||
#pragma comment(lib, "hid.lib")
|
||||
|
||||
namespace
|
||||
{
|
||||
bool Dri_Hid_BeginRead(Dri_Hid_Struct_ReadPort* p_Port, QString* p_TextStatus)
|
||||
{
|
||||
if (!p_Port->IsOpened || (p_Port->InputLength == 0)) return false;
|
||||
if (p_Port->IsReadPending) return true;
|
||||
|
||||
ResetEvent(p_Port->HandleEvent);
|
||||
p_Port->ReadBuffer.fill(0, p_Port->InputLength);
|
||||
p_Port->OverlappedRead = {};
|
||||
p_Port->OverlappedRead.hEvent = p_Port->HandleEvent;
|
||||
|
||||
DWORD BytesRead = 0;
|
||||
if (ReadFile(
|
||||
p_Port->HandleRead,
|
||||
p_Port->ReadBuffer.data(),
|
||||
p_Port->InputLength,
|
||||
&BytesRead,
|
||||
&p_Port->OverlappedRead) ||
|
||||
(GetLastError() == ERROR_IO_PENDING))
|
||||
{
|
||||
p_Port->IsReadPending = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (p_TextStatus != nullptr) *p_TextStatus = QStringLiteral("%1 failed to start async read: %2").arg(p_Port->PortName).arg(GetLastError());
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void Dri_Hid_CloseReadPort(Dri_Hid_Struct_ReadPort* p_Port)
|
||||
{
|
||||
if ((p_Port->HandleRead != INVALID_HANDLE_VALUE) && p_Port->IsReadPending) CancelIoEx(p_Port->HandleRead, &p_Port->OverlappedRead);
|
||||
if (p_Port->HandleRead != INVALID_HANDLE_VALUE) CloseHandle(p_Port->HandleRead);
|
||||
if (p_Port->HandleEvent != nullptr) CloseHandle(p_Port->HandleEvent);
|
||||
*p_Port = Dri_Hid_Struct_ReadPort();
|
||||
}
|
||||
|
||||
bool Dri_Hid_InitReadPort(
|
||||
Dri_Hid_Struct_ReadPort* p_Port,
|
||||
const QString& DevicePath,
|
||||
quint16 InputLength,
|
||||
Mid_Enum_RawPacketSource PacketSource,
|
||||
const QString& PortName,
|
||||
QString* p_TextStatus)
|
||||
{
|
||||
Dri_Hid_CloseReadPort(p_Port);
|
||||
|
||||
p_Port->HandleRead = CreateFileW(
|
||||
reinterpret_cast<LPCWSTR>(DevicePath.utf16()),
|
||||
GENERIC_READ,
|
||||
FILE_SHARE_READ | FILE_SHARE_WRITE,
|
||||
nullptr,
|
||||
OPEN_EXISTING,
|
||||
FILE_FLAG_OVERLAPPED,
|
||||
nullptr);
|
||||
if (p_Port->HandleRead == INVALID_HANDLE_VALUE)
|
||||
{
|
||||
if (p_TextStatus != nullptr) *p_TextStatus = QStringLiteral("%1 failed to open read handle: %2").arg(PortName).arg(GetLastError());
|
||||
return false;
|
||||
}
|
||||
|
||||
p_Port->HandleEvent = CreateEventW(nullptr, TRUE, FALSE, nullptr);
|
||||
if (p_Port->HandleEvent == nullptr)
|
||||
{
|
||||
if (p_TextStatus != nullptr) *p_TextStatus = QStringLiteral("%1 failed to create event: %2").arg(PortName).arg(GetLastError());
|
||||
Dri_Hid_CloseReadPort(p_Port);
|
||||
return false;
|
||||
}
|
||||
|
||||
p_Port->InputLength = InputLength;
|
||||
p_Port->PacketSource = PacketSource;
|
||||
p_Port->PortName = PortName;
|
||||
p_Port->ReadBuffer = QByteArray(InputLength, 0);
|
||||
p_Port->OverlappedRead.hEvent = p_Port->HandleEvent;
|
||||
p_Port->IsOpened = true;
|
||||
|
||||
if (!Dri_Hid_BeginRead(p_Port, p_TextStatus)) { Dri_Hid_CloseReadPort(p_Port); return false; }
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Dri_Hid_Read(
|
||||
Dri_Hid_Struct_ReadPort* p_Port,
|
||||
Mid_Struct_RawPacket* p_Packet,
|
||||
QString* p_TextStatus)
|
||||
{
|
||||
*p_Packet = Mid_Struct_RawPacket();
|
||||
p_Packet->Source = p_Port->PacketSource;
|
||||
p_Packet->PortName = p_Port->PortName;
|
||||
|
||||
if (!p_Port->IsOpened) return false;
|
||||
if (!p_Port->IsReadPending)
|
||||
{
|
||||
Dri_Hid_BeginRead(p_Port, p_TextStatus);
|
||||
return false;
|
||||
}
|
||||
|
||||
DWORD BytesRead = 0;
|
||||
if (!GetOverlappedResult(p_Port->HandleRead, &p_Port->OverlappedRead, &BytesRead, FALSE))
|
||||
{
|
||||
const DWORD ErrorCode = GetLastError();
|
||||
if (ErrorCode == ERROR_IO_INCOMPLETE) return false;
|
||||
if (p_TextStatus != nullptr) *p_TextStatus = QStringLiteral("%1 read packet failed: %2").arg(p_Port->PortName).arg(ErrorCode);
|
||||
Dri_Hid_CloseReadPort(p_Port);
|
||||
return false;
|
||||
}
|
||||
|
||||
p_Port->IsReadPending = false;
|
||||
if (BytesRead > 0) { p_Packet->IsValid = true; p_Packet->ByteArray = p_Port->ReadBuffer.left(static_cast<int>(BytesRead)); }
|
||||
|
||||
Dri_Hid_BeginRead(p_Port, p_TextStatus);
|
||||
return p_Packet->IsValid;
|
||||
}
|
||||
|
||||
HANDLE Dri_Hid_OpenWriteHandle(const QString& DevicePath, QString* p_TextError)
|
||||
{
|
||||
HANDLE HandleWrite = CreateFileW(
|
||||
reinterpret_cast<LPCWSTR>(DevicePath.utf16()),
|
||||
GENERIC_WRITE,
|
||||
FILE_SHARE_READ | FILE_SHARE_WRITE,
|
||||
nullptr,
|
||||
OPEN_EXISTING,
|
||||
0,
|
||||
nullptr);
|
||||
if (HandleWrite != INVALID_HANDLE_VALUE) return HandleWrite;
|
||||
|
||||
const DWORD WriteError = GetLastError();
|
||||
HandleWrite = CreateFileW(
|
||||
reinterpret_cast<LPCWSTR>(DevicePath.utf16()),
|
||||
0,
|
||||
FILE_SHARE_READ | FILE_SHARE_WRITE,
|
||||
nullptr,
|
||||
OPEN_EXISTING,
|
||||
0,
|
||||
nullptr);
|
||||
if ((HandleWrite == INVALID_HANDLE_VALUE) && (p_TextError != nullptr)) *p_TextError = QStringLiteral("GENERIC_WRITE=%1, ZeroAccess=%2").arg(WriteError).arg(GetLastError());
|
||||
return HandleWrite;
|
||||
}
|
||||
|
||||
bool Dri_Hid_WritePacket(
|
||||
HANDLE HandleWrite,
|
||||
quint16 OutputLength,
|
||||
const QByteArray& Packet,
|
||||
QString* p_TextError)
|
||||
{
|
||||
if ((HandleWrite == INVALID_HANDLE_VALUE) || Packet.isEmpty()) return false;
|
||||
|
||||
QByteArray Buffer = Packet;
|
||||
if (HidD_SetOutputReport(HandleWrite, Buffer.data(), static_cast<ULONG>(Buffer.size()))) return true;
|
||||
|
||||
const DWORD SetReportError = GetLastError();
|
||||
DWORD BytesWritten = 0;
|
||||
if (WriteFile(HandleWrite, Buffer.constData(), static_cast<DWORD>(Buffer.size()), &BytesWritten, nullptr) && (BytesWritten == static_cast<DWORD>(Buffer.size()))) return true;
|
||||
|
||||
const DWORD WriteFileError = GetLastError();
|
||||
if ((OutputLength > 0) && (Buffer.size() < OutputLength))
|
||||
{
|
||||
Buffer.append(OutputLength - Buffer.size(), 0);
|
||||
|
||||
if (HidD_SetOutputReport(HandleWrite, Buffer.data(), static_cast<ULONG>(Buffer.size()))) return true;
|
||||
|
||||
const DWORD PaddedSetReportError = GetLastError();
|
||||
BytesWritten = 0;
|
||||
if (WriteFile(HandleWrite, Buffer.constData(), static_cast<DWORD>(Buffer.size()), &BytesWritten, nullptr) && (BytesWritten == static_cast<DWORD>(Buffer.size()))) return true;
|
||||
|
||||
const DWORD PaddedWriteFileError = GetLastError();
|
||||
if (p_TextError != nullptr)
|
||||
{
|
||||
*p_TextError = QStringLiteral(
|
||||
"SetOutputReport=%1, WriteFile=%2, PaddedSetOutputReport=%3, PaddedWriteFile=%4")
|
||||
.arg(SetReportError)
|
||||
.arg(WriteFileError)
|
||||
.arg(PaddedSetReportError)
|
||||
.arg(PaddedWriteFileError);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (p_TextError != nullptr) *p_TextError = QStringLiteral("SetOutputReport=%1, WriteFile=%2").arg(SetReportError).arg(WriteFileError);
|
||||
return false;
|
||||
}
|
||||
|
||||
41
DRI/Dri_Hid.h
Normal file
41
DRI/Dri_Hid.h
Normal file
@@ -0,0 +1,41 @@
|
||||
#pragma once
|
||||
|
||||
#include "MID/Mid_Def.h"
|
||||
#include <QtCore/QByteArray>
|
||||
#include <QtCore/QString>
|
||||
#include <Windows.h>
|
||||
|
||||
// Shared HID async read/write helpers used by USB and BLE HID paths.
|
||||
struct Dri_Hid_Struct_ReadPort
|
||||
{
|
||||
HANDLE HandleRead = INVALID_HANDLE_VALUE;
|
||||
HANDLE HandleEvent = nullptr;
|
||||
OVERLAPPED OverlappedRead = {};
|
||||
bool IsOpened = false;
|
||||
bool IsReadPending = false;
|
||||
quint16 InputLength = 0;
|
||||
Mid_Enum_RawPacketSource PacketSource = Mid_Enum_RawPacketSource_None;
|
||||
QString PortName;
|
||||
QByteArray ReadBuffer;
|
||||
};
|
||||
|
||||
void Dri_Hid_CloseReadPort(Dri_Hid_Struct_ReadPort* p_Port);
|
||||
bool Dri_Hid_InitReadPort(
|
||||
Dri_Hid_Struct_ReadPort* p_Port,
|
||||
const QString& DevicePath,
|
||||
quint16 InputLength,
|
||||
Mid_Enum_RawPacketSource PacketSource,
|
||||
const QString& PortName,
|
||||
QString* p_TextStatus);
|
||||
bool Dri_Hid_Read(
|
||||
Dri_Hid_Struct_ReadPort* p_Port,
|
||||
Mid_Struct_RawPacket* p_Packet,
|
||||
QString* p_TextStatus);
|
||||
|
||||
HANDLE Dri_Hid_OpenWriteHandle(const QString& DevicePath, QString* p_TextError);
|
||||
bool Dri_Hid_WritePacket(
|
||||
HANDLE HandleWrite,
|
||||
quint16 OutputLength,
|
||||
const QByteArray& Packet,
|
||||
QString* p_TextError);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#include "DRI/Dri_NkroRaw.h"
|
||||
#include "DRI/Dri_NkroRaw.h"
|
||||
|
||||
#include <QtCore/QVector>
|
||||
#include <Windows.h>
|
||||
@@ -6,9 +6,9 @@
|
||||
namespace
|
||||
{
|
||||
|
||||
/* ---------- 设备过滤与 Usage 映射 ---------- */
|
||||
// Device filter and scancode-to-usage mapping.
|
||||
|
||||
QString Dri_NkroRaw_Func_GetDevicePath(HANDLE DeviceHandle)
|
||||
QString Dri_NkroRaw_GetDevicePath(HANDLE DeviceHandle)
|
||||
{
|
||||
if (DeviceHandle == nullptr)
|
||||
{
|
||||
@@ -31,7 +31,7 @@ QString Dri_NkroRaw_Func_GetDevicePath(HANDLE DeviceHandle)
|
||||
return QString::fromWCharArray(Buffer.constData()).trimmed();
|
||||
}
|
||||
|
||||
bool Dri_NkroRaw_Func_IsTargetDevice(const QString& DevicePath, const Mid_Struct_DeviceConfig& DeviceConfig)
|
||||
bool Dri_NkroRaw_IsTargetDevice(const QString& DevicePath, const Mid_Struct_DeviceConfig& DeviceConfig)
|
||||
{
|
||||
if (DevicePath.isEmpty())
|
||||
{
|
||||
@@ -43,7 +43,7 @@ bool Dri_NkroRaw_Func_IsTargetDevice(const QString& DevicePath, const Mid_Struct
|
||||
UpperPath.contains(QStringLiteral("PID_%1").arg(DeviceConfig.ProductId, 4, 16, QLatin1Char('0')).toUpper());
|
||||
}
|
||||
|
||||
quint16 Dri_NkroRaw_Func_GetUsage(const RAWKEYBOARD& Keyboard)
|
||||
quint16 Dri_NkroRaw_GetUsage(const RAWKEYBOARD& Keyboard)
|
||||
{
|
||||
const bool IsE0 = (Keyboard.Flags & RI_KEY_E0) != 0;
|
||||
const bool IsE1 = (Keyboard.Flags & RI_KEY_E1) != 0;
|
||||
@@ -97,20 +97,18 @@ quint16 Dri_NkroRaw_Func_GetUsage(const RAWKEYBOARD& Keyboard)
|
||||
|
||||
} // namespace
|
||||
|
||||
/* ---------- 生命周期 ---------- */
|
||||
|
||||
void Dri_NkroRaw_Func_Close(Dri_NkroRaw_Struct_Port* p_Port)
|
||||
void Dri_NkroRaw_Close(Dri_NkroRaw_Struct_Port* p_Port)
|
||||
{
|
||||
*p_Port = Dri_NkroRaw_Struct_Port();
|
||||
}
|
||||
|
||||
bool Dri_NkroRaw_Func_Open(
|
||||
bool Dri_NkroRaw_Init(
|
||||
Dri_NkroRaw_Struct_Port* p_Port,
|
||||
const Mid_Struct_DeviceConfig& DeviceConfig,
|
||||
void* WindowHandle,
|
||||
QString* p_TextStatus)
|
||||
{
|
||||
Dri_NkroRaw_Func_Close(p_Port);
|
||||
Dri_NkroRaw_Close(p_Port);
|
||||
|
||||
if (WindowHandle == nullptr)
|
||||
{
|
||||
@@ -147,9 +145,7 @@ bool Dri_NkroRaw_Func_Open(
|
||||
return true;
|
||||
}
|
||||
|
||||
/* ---------- RawInput 主流程 ---------- */
|
||||
|
||||
bool Dri_NkroRaw_Func_HandleNativeMessage(
|
||||
bool Dri_NkroRaw_HandleMessage(
|
||||
Dri_NkroRaw_Struct_Port* p_Port,
|
||||
void* p_Message,
|
||||
QString* p_TextStatus)
|
||||
@@ -189,13 +185,13 @@ bool Dri_NkroRaw_Func_HandleNativeMessage(
|
||||
return false;
|
||||
}
|
||||
|
||||
const QString DevicePath = Dri_NkroRaw_Func_GetDevicePath(p_Input->header.hDevice);
|
||||
if (!Dri_NkroRaw_Func_IsTargetDevice(DevicePath, p_Port->DeviceConfig))
|
||||
const QString DevicePath = Dri_NkroRaw_GetDevicePath(p_Input->header.hDevice);
|
||||
if (!Dri_NkroRaw_IsTargetDevice(DevicePath, p_Port->DeviceConfig))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const quint16 Usage = Dri_NkroRaw_Func_GetUsage(p_Input->data.keyboard);
|
||||
const quint16 Usage = Dri_NkroRaw_GetUsage(p_Input->data.keyboard);
|
||||
if (Usage == 0)
|
||||
{
|
||||
return false;
|
||||
@@ -249,6 +245,7 @@ bool Dri_NkroRaw_Func_HandleNativeMessage(
|
||||
|
||||
Mid_Struct_RawPacket Packet;
|
||||
Packet.IsValid = true;
|
||||
Packet.Source = Mid_Enum_RawPacketSource_UsbNkroRaw;
|
||||
Packet.PortName = QStringLiteral("NKRO(原生输入)");
|
||||
Packet.ByteArray = QByteArray(MID_CONST_PACKET_SIZE_NKRO, 0);
|
||||
Packet.ByteArray[0] = static_cast<char>(Mid_Enum_ReportId_Nkro);
|
||||
@@ -263,12 +260,13 @@ bool Dri_NkroRaw_Func_HandleNativeMessage(
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Dri_NkroRaw_Func_Read(
|
||||
bool Dri_NkroRaw_Read(
|
||||
Dri_NkroRaw_Struct_Port* p_Port,
|
||||
Mid_Struct_RawPacket* p_Packet,
|
||||
QString*)
|
||||
{
|
||||
p_Packet->IsValid = false;
|
||||
p_Packet->Source = Mid_Enum_RawPacketSource_UsbNkroRaw;
|
||||
p_Packet->ByteArray.clear();
|
||||
p_Packet->PortName = QStringLiteral("NKRO(原生输入)");
|
||||
|
||||
@@ -280,3 +278,4 @@ bool Dri_NkroRaw_Func_Read(
|
||||
*p_Packet = p_Port->PacketQueue.takeFirst();
|
||||
return p_Packet->IsValid;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,40 +1,31 @@
|
||||
#pragma once
|
||||
#pragma once
|
||||
|
||||
#include "MID/Mid_Def.h"
|
||||
#include <QtCore/QByteArray>
|
||||
#include <QtCore/QList>
|
||||
#include <QtCore/QString>
|
||||
|
||||
/*
|
||||
* DRI NKRO RAW 层:负责注册 Windows RAWINPUT,接收键盘 104+ 通道。
|
||||
* 这里集中保存窗口句柄、设备配置、按键队列与 Modifier/Bitmap 缓冲。
|
||||
*/
|
||||
// Windows RAWINPUT reader for the NKRO path.
|
||||
struct Dri_NkroRaw_Struct_Port
|
||||
{
|
||||
/* 运行状态:是否已注册 RAWINPUT 以及宿主窗口 HWND */
|
||||
bool IsOpened = false;
|
||||
void* WindowHandle = nullptr;
|
||||
Mid_Struct_DeviceConfig DeviceConfig;
|
||||
/* 最新的 Modifier 字节和 NKRO Usage 位图 */
|
||||
quint8 Modifier = 0;
|
||||
QByteArray UsageBitmap = QByteArray(MID_CONST_USAGE_BITMAP_SIZE, 0);
|
||||
/* RAWINPUT 读取结果放入 PacketQueue,供上层逻辑顺序消费 */
|
||||
QList<Mid_Struct_RawPacket> PacketQueue;
|
||||
QString DevicePath;
|
||||
};
|
||||
|
||||
/* 注销 RAWINPUT 并清空所有缓存。 */
|
||||
void Dri_NkroRaw_Func_Close(Dri_NkroRaw_Struct_Port* p_Port);
|
||||
/* 注册 RAWINPUT:绑定目标窗口并监听指定 VID/PID。 */
|
||||
bool Dri_NkroRaw_Func_Open(Dri_NkroRaw_Struct_Port* p_Port,
|
||||
void Dri_NkroRaw_Close(Dri_NkroRaw_Struct_Port* p_Port);
|
||||
bool Dri_NkroRaw_Init(Dri_NkroRaw_Struct_Port* p_Port,
|
||||
const Mid_Struct_DeviceConfig& DeviceConfig,
|
||||
void* WindowHandle,
|
||||
QString* p_TextStatus);
|
||||
/* 把 Windows 消息转为 NKRO 数据:核心教学入口。 */
|
||||
bool Dri_NkroRaw_Func_HandleNativeMessage(Dri_NkroRaw_Struct_Port* p_Port,
|
||||
bool Dri_NkroRaw_HandleMessage(Dri_NkroRaw_Struct_Port* p_Port,
|
||||
void* p_Message,
|
||||
QString* p_TextStatus);
|
||||
/* 上层每次取一个封装好的 RawPacket。 */
|
||||
bool Dri_NkroRaw_Func_Read(Dri_NkroRaw_Struct_Port* p_Port,
|
||||
bool Dri_NkroRaw_Read(Dri_NkroRaw_Struct_Port* p_Port,
|
||||
Mid_Struct_RawPacket* p_Packet,
|
||||
QString* p_TextStatus);
|
||||
|
||||
|
||||
920
DRI/Dri_Nus.cpp
Normal file
920
DRI/Dri_Nus.cpp
Normal file
@@ -0,0 +1,920 @@
|
||||
#include "DRI/Dri_Nus.h"
|
||||
|
||||
#include "COM/Com_Protocol.h"
|
||||
#include <QtBluetooth/QBluetoothDeviceDiscoveryAgent>
|
||||
#include <QtBluetooth/QBluetoothDeviceInfo>
|
||||
#include <QtBluetooth/QBluetoothUuid>
|
||||
#include <QtBluetooth/QLowEnergyCharacteristic>
|
||||
#include <QtBluetooth/QLowEnergyController>
|
||||
#include <QtBluetooth/QLowEnergyDescriptor>
|
||||
#include <QtBluetooth/QLowEnergyService>
|
||||
#include <QtCore/QCoreApplication>
|
||||
#include <QtCore/QQueue>
|
||||
#include <QtCore/QTimer>
|
||||
#include <QtCore/QVector>
|
||||
|
||||
struct Dri_Nus_Struct_Candidate
|
||||
{
|
||||
QBluetoothDeviceInfo DeviceInfo;
|
||||
QString EndpointId;
|
||||
QString DeviceLabel;
|
||||
QString DeviceName;
|
||||
QString AddressText;
|
||||
};
|
||||
|
||||
struct Dri_Nus_Struct_Context
|
||||
{
|
||||
QBluetoothDeviceDiscoveryAgent* p_DiscoveryAgent = nullptr;
|
||||
QLowEnergyController* p_Controller = nullptr;
|
||||
QLowEnergyService* p_Service = nullptr;
|
||||
|
||||
QVector<Dri_Nus_Struct_Candidate> CandidateList;
|
||||
int CurrentCandidateIndex = -1;
|
||||
int LockedCandidateIndex = -1;
|
||||
bool IsDiscoveryFinished = false;
|
||||
bool HasTargetService = false;
|
||||
|
||||
QLowEnergyCharacteristic WriteCharacteristic;
|
||||
QLowEnergyCharacteristic NotifyCharacteristic;
|
||||
QLowEnergyDescriptor NotifyDescriptor;
|
||||
|
||||
QQueue<Com_Struct_RawPacket> PacketQueue;
|
||||
};
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
const QString kPreferredBleDeviceName = QStringLiteral("WH Mini Keyboard");
|
||||
const QBluetoothUuid kServiceUuid(
|
||||
QUuid(QStringLiteral("6E400001-B5A3-F393-E0A9-E50E24DCCA9E")));
|
||||
const QBluetoothUuid kRxCharacteristicUuid(
|
||||
QUuid(QStringLiteral("6E400002-B5A3-F393-E0A9-E50E24DCCA9E")));
|
||||
const QBluetoothUuid kTxCharacteristicUuid(
|
||||
QUuid(QStringLiteral("6E400003-B5A3-F393-E0A9-E50E24DCCA9E")));
|
||||
|
||||
QString Dri_Nus_NormalizeAddressText(const QBluetoothAddress& Address)
|
||||
{
|
||||
const QString AddressText = Address.toString().trimmed().toUpper();
|
||||
return (AddressText == QStringLiteral("00:00:00:00:00:00"))
|
||||
? QString()
|
||||
: AddressText;
|
||||
}
|
||||
|
||||
bool Dri_Nus_IsPreferredDeviceName(const QString& DeviceName)
|
||||
{
|
||||
return DeviceName.trimmed().compare(kPreferredBleDeviceName, Qt::CaseInsensitive) == 0;
|
||||
}
|
||||
|
||||
QString Dri_Nus_FormatCandidateSummary(const Dri_Nus_Struct_Candidate& Candidate)
|
||||
{
|
||||
QString Summary = Candidate.DeviceLabel;
|
||||
if (!Candidate.AddressText.isEmpty())
|
||||
{
|
||||
Summary += QStringLiteral(" [%1]").arg(Candidate.AddressText);
|
||||
}
|
||||
|
||||
return Summary;
|
||||
}
|
||||
|
||||
QString Dri_Nus_GetPacketTypeText(Com_Enum_ProtocolType Type)
|
||||
{
|
||||
switch (Type)
|
||||
{
|
||||
case Com_Enum_ProtocolType_HelloReq: return QStringLiteral("HelloReq");
|
||||
case Com_Enum_ProtocolType_HelloRsp: return QStringLiteral("HelloRsp");
|
||||
case Com_Enum_ProtocolType_Bitmap: return QStringLiteral("Bitmap");
|
||||
case Com_Enum_ProtocolType_FunctionKeyEvent: return QStringLiteral("FunctionKeyEvent");
|
||||
case Com_Enum_ProtocolType_LedState: return QStringLiteral("LedState");
|
||||
case Com_Enum_ProtocolType_TimeSync: return QStringLiteral("TimeSync");
|
||||
case Com_Enum_ProtocolType_ThemeRgb: return QStringLiteral("ThemeRgb");
|
||||
case Com_Enum_ProtocolType_Ack: return QStringLiteral("Ack");
|
||||
case Com_Enum_ProtocolType_Error: return QStringLiteral("Error");
|
||||
default: return QStringLiteral("Unknown");
|
||||
}
|
||||
}
|
||||
|
||||
const Dri_Nus_Struct_Candidate* Dri_Nus_GetCurrentCandidate(
|
||||
const Dri_Nus_Struct_Context* p_Context)
|
||||
{
|
||||
if ((p_Context == nullptr) ||
|
||||
(p_Context->CurrentCandidateIndex < 0) ||
|
||||
(p_Context->CurrentCandidateIndex >= p_Context->CandidateList.size()))
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return &p_Context->CandidateList.at(p_Context->CurrentCandidateIndex);
|
||||
}
|
||||
|
||||
QString Dri_Nus_BuildEndpointId(
|
||||
const QBluetoothDeviceInfo& DeviceInfo,
|
||||
int CandidateOrdinal)
|
||||
{
|
||||
QString DeviceUuidText = DeviceInfo.deviceUuid().toString().trimmed();
|
||||
DeviceUuidText.remove(QLatin1Char('{'));
|
||||
DeviceUuidText.remove(QLatin1Char('}'));
|
||||
if (!DeviceUuidText.isEmpty())
|
||||
{
|
||||
return QStringLiteral("uuid:%1").arg(DeviceUuidText.toUpper());
|
||||
}
|
||||
|
||||
const QString AddressText = DeviceInfo.address().toString().trimmed().toUpper();
|
||||
if (!AddressText.isEmpty() &&
|
||||
(AddressText != QStringLiteral("00:00:00:00:00:00")))
|
||||
{
|
||||
return QStringLiteral("addr:%1").arg(AddressText);
|
||||
}
|
||||
|
||||
const QString DeviceName = DeviceInfo.name().trimmed();
|
||||
if (!DeviceName.isEmpty())
|
||||
{
|
||||
return QStringLiteral("name:%1#%2").arg(DeviceName).arg(CandidateOrdinal);
|
||||
}
|
||||
|
||||
return QStringLiteral("candidate:%1").arg(CandidateOrdinal);
|
||||
}
|
||||
|
||||
bool Dri_Nus_HasStableConnectIdentity(const QBluetoothDeviceInfo& DeviceInfo)
|
||||
{
|
||||
QString DeviceUuidText = DeviceInfo.deviceUuid().toString().trimmed();
|
||||
DeviceUuidText.remove(QLatin1Char('{'));
|
||||
DeviceUuidText.remove(QLatin1Char('}'));
|
||||
if (!DeviceUuidText.isEmpty())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
const QString AddressText = DeviceInfo.address().toString().trimmed().toUpper();
|
||||
return !AddressText.isEmpty() &&
|
||||
(AddressText != QStringLiteral("00:00:00:00:00:00"));
|
||||
}
|
||||
|
||||
bool Dri_Nus_IsCurrentCandidate(
|
||||
const Dri_Nus_Struct_Context* p_Context,
|
||||
const QString& EndpointId)
|
||||
{
|
||||
const Dri_Nus_Struct_Candidate* p_CurrentCandidate = Dri_Nus_GetCurrentCandidate(p_Context);
|
||||
return (p_CurrentCandidate != nullptr) &&
|
||||
(p_CurrentCandidate->EndpointId.compare(EndpointId, Qt::CaseInsensitive) == 0);
|
||||
}
|
||||
|
||||
void Dri_Nus_ResetServiceState(Dri_Nus_Struct_Port* p_Port)
|
||||
{
|
||||
Dri_Nus_Struct_Context* p_Context = p_Port->p_Context;
|
||||
if (p_Context == nullptr)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
p_Context->p_Service = nullptr;
|
||||
p_Context->WriteCharacteristic = QLowEnergyCharacteristic();
|
||||
p_Context->NotifyCharacteristic = QLowEnergyCharacteristic();
|
||||
p_Context->NotifyDescriptor = QLowEnergyDescriptor();
|
||||
p_Context->HasTargetService = false;
|
||||
p_Port->IsConnected = false;
|
||||
p_Port->HasWriteAck = false;
|
||||
}
|
||||
|
||||
void Dri_Nus_ReleaseConnectionObjects(Dri_Nus_Struct_Context* p_Context)
|
||||
{
|
||||
if (p_Context == nullptr)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
p_Context->p_Service = nullptr;
|
||||
p_Context->WriteCharacteristic = QLowEnergyCharacteristic();
|
||||
p_Context->NotifyCharacteristic = QLowEnergyCharacteristic();
|
||||
p_Context->NotifyDescriptor = QLowEnergyDescriptor();
|
||||
p_Context->HasTargetService = false;
|
||||
|
||||
if (p_Context->p_Controller != nullptr)
|
||||
{
|
||||
p_Context->p_Controller->disconnect();
|
||||
p_Context->p_Controller->deleteLater();
|
||||
p_Context->p_Controller = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void Dri_Nus_QueuePacket(
|
||||
Dri_Nus_Struct_Context* p_Context,
|
||||
const QByteArray& PacketBody,
|
||||
const QString& PortName,
|
||||
const QString& EndpointId)
|
||||
{
|
||||
if ((p_Context == nullptr) || PacketBody.isEmpty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Com_Struct_RawPacket Packet;
|
||||
Packet.IsValid = Com_Protocol_DecodeMessageType(PacketBody, &Packet.ProtocolType);
|
||||
Packet.Source = Com_Enum_RawPacketSource_BleNus;
|
||||
Packet.PortName = PortName;
|
||||
Packet.EndpointId = EndpointId;
|
||||
Packet.ByteArray = PacketBody;
|
||||
|
||||
if (Packet.IsValid)
|
||||
{
|
||||
p_Context->PacketQueue.enqueue(Packet);
|
||||
}
|
||||
}
|
||||
|
||||
void Dri_Nus_DeleteContext(Dri_Nus_Struct_Context* p_Context)
|
||||
{
|
||||
if (p_Context == nullptr)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (p_Context->p_DiscoveryAgent != nullptr)
|
||||
{
|
||||
p_Context->p_DiscoveryAgent->stop();
|
||||
}
|
||||
|
||||
if (p_Context->p_Controller != nullptr)
|
||||
{
|
||||
p_Context->p_Controller->disconnect();
|
||||
p_Context->p_Controller->disconnectFromDevice();
|
||||
delete p_Context->p_Controller;
|
||||
p_Context->p_Controller = nullptr;
|
||||
}
|
||||
|
||||
delete p_Context->p_DiscoveryAgent;
|
||||
delete p_Context;
|
||||
}
|
||||
|
||||
bool Dri_Nus_StartNextCandidate(Dri_Nus_Struct_Port* p_Port);
|
||||
|
||||
void Dri_Nus_AdvanceFromCurrentCandidate(
|
||||
Dri_Nus_Struct_Port* p_Port,
|
||||
const QString& TextStatus)
|
||||
{
|
||||
Dri_Nus_Struct_Context* p_Context = p_Port->p_Context;
|
||||
if (p_Context == nullptr)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const bool IsLockedCandidate =
|
||||
(p_Context->LockedCandidateIndex >= 0) &&
|
||||
(p_Context->LockedCandidateIndex == p_Context->CurrentCandidateIndex);
|
||||
|
||||
Dri_Nus_ResetServiceState(p_Port);
|
||||
Dri_Nus_ReleaseConnectionObjects(p_Context);
|
||||
p_Port->TextEndpointSummary = TextStatus;
|
||||
|
||||
if (IsLockedCandidate)
|
||||
{
|
||||
// 宸茬‘璁ょ洰鏍囪澶囨柇寮€鍚庯紝鏈疆浼氳瘽蹇呴』缁撴潫锛涙槸鍚﹂噸鍚灇涓剧敱涓婂眰浼氳瘽閫昏緫鍐冲畾銆? p_Context->LockedCandidateIndex = -1;
|
||||
p_Port->IsOpened = false;
|
||||
return;
|
||||
}
|
||||
|
||||
QTimer::singleShot(
|
||||
0,
|
||||
QCoreApplication::instance(),
|
||||
[p_Port]()
|
||||
{
|
||||
Dri_Nus_StartNextCandidate(p_Port);
|
||||
});
|
||||
}
|
||||
|
||||
void Dri_Nus_AttachServiceSignals(
|
||||
Dri_Nus_Struct_Port* p_Port,
|
||||
Dri_Nus_Struct_Context* p_Context,
|
||||
const QString& EndpointId,
|
||||
const QString& DeviceLabel)
|
||||
{
|
||||
QObject::connect(
|
||||
p_Context->p_Service,
|
||||
&QLowEnergyService::stateChanged,
|
||||
[p_Port, p_Context, EndpointId, DeviceLabel](QLowEnergyService::ServiceState State)
|
||||
{
|
||||
if (!Dri_Nus_IsCurrentCandidate(p_Context, EndpointId))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (State != QLowEnergyService::ServiceDiscovered)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
p_Context->WriteCharacteristic =
|
||||
p_Context->p_Service->characteristic(kRxCharacteristicUuid);
|
||||
p_Context->NotifyCharacteristic =
|
||||
p_Context->p_Service->characteristic(kTxCharacteristicUuid);
|
||||
|
||||
if (!p_Context->WriteCharacteristic.isValid() ||
|
||||
!p_Context->NotifyCharacteristic.isValid())
|
||||
{
|
||||
Dri_Nus_AdvanceFromCurrentCandidate(
|
||||
p_Port,
|
||||
QStringLiteral("BLE candidate %1 is missing NUS characteristics. Try the next one.")
|
||||
.arg(DeviceLabel));
|
||||
return;
|
||||
}
|
||||
|
||||
p_Context->NotifyDescriptor = p_Context->NotifyCharacteristic.descriptor(
|
||||
QBluetoothUuid(QBluetoothUuid::ClientCharacteristicConfiguration));
|
||||
if (p_Context->NotifyDescriptor.isValid())
|
||||
{
|
||||
p_Context->p_Service->writeDescriptor(
|
||||
p_Context->NotifyDescriptor,
|
||||
QByteArray::fromHex("0100"));
|
||||
return;
|
||||
}
|
||||
|
||||
p_Port->IsConnected = true;
|
||||
p_Port->TextEndpointSummary =
|
||||
QStringLiteral("NUS service is ready on %1").arg(DeviceLabel);
|
||||
});
|
||||
|
||||
QObject::connect(
|
||||
p_Context->p_Service,
|
||||
&QLowEnergyService::descriptorWritten,
|
||||
[p_Port, p_Context, EndpointId, DeviceLabel](
|
||||
const QLowEnergyDescriptor& Descriptor,
|
||||
const QByteArray&)
|
||||
{
|
||||
if (!Dri_Nus_IsCurrentCandidate(p_Context, EndpointId))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (Descriptor != p_Context->NotifyDescriptor)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
p_Port->IsConnected = true;
|
||||
p_Port->TextEndpointSummary =
|
||||
QStringLiteral("NUS notify is ready on %1").arg(DeviceLabel);
|
||||
});
|
||||
|
||||
QObject::connect(
|
||||
p_Context->p_Service,
|
||||
&QLowEnergyService::characteristicChanged,
|
||||
[p_Context, EndpointId, DeviceLabel](
|
||||
const QLowEnergyCharacteristic& Characteristic,
|
||||
const QByteArray& Value)
|
||||
{
|
||||
if (!Dri_Nus_IsCurrentCandidate(p_Context, EndpointId))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (Characteristic.uuid() != kTxCharacteristicUuid)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Dri_Nus_QueuePacket(
|
||||
p_Context,
|
||||
Value,
|
||||
QStringLiteral("BLE NUS (%1)").arg(DeviceLabel),
|
||||
EndpointId);
|
||||
});
|
||||
|
||||
QObject::connect(
|
||||
p_Context->p_Service,
|
||||
&QLowEnergyService::characteristicWritten,
|
||||
[p_Port, p_Context, EndpointId, DeviceLabel](
|
||||
const QLowEnergyCharacteristic& Characteristic,
|
||||
const QByteArray& Value)
|
||||
{
|
||||
if (!Dri_Nus_IsCurrentCandidate(p_Context, EndpointId))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (Characteristic.uuid() != kRxCharacteristicUuid)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Com_Enum_ProtocolType PacketType = Com_Enum_ProtocolType_None;
|
||||
const QString PacketTypeText =
|
||||
Com_Protocol_DecodeMessageType(Value, &PacketType)
|
||||
? Dri_Nus_GetPacketTypeText(PacketType)
|
||||
: QStringLiteral("unknown");
|
||||
p_Port->TextEndpointSummary =
|
||||
QStringLiteral("BLE write acknowledged by RX characteristic: %1 (%2)")
|
||||
.arg(DeviceLabel, PacketTypeText);
|
||||
p_Port->HasWriteAck = true;
|
||||
});
|
||||
|
||||
QObject::connect(
|
||||
p_Context->p_Service,
|
||||
static_cast<void (QLowEnergyService::*)(QLowEnergyService::ServiceError)>(
|
||||
&QLowEnergyService::error),
|
||||
[p_Port, p_Context, EndpointId, DeviceLabel](QLowEnergyService::ServiceError)
|
||||
{
|
||||
if (!Dri_Nus_IsCurrentCandidate(p_Context, EndpointId))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Dri_Nus_AdvanceFromCurrentCandidate(
|
||||
p_Port,
|
||||
QStringLiteral("BLE candidate %1 has a NUS service error.").arg(DeviceLabel));
|
||||
});
|
||||
|
||||
p_Context->p_Service->discoverDetails();
|
||||
}
|
||||
|
||||
bool Dri_Nus_StartController(
|
||||
Dri_Nus_Struct_Port* p_Port,
|
||||
Dri_Nus_Struct_Context* p_Context,
|
||||
int CandidateIndex)
|
||||
{
|
||||
if ((p_Context == nullptr) || (p_Context->p_Controller != nullptr))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if ((CandidateIndex < 0) || (CandidateIndex >= p_Context->CandidateList.size()))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const Dri_Nus_Struct_Candidate& Candidate = p_Context->CandidateList.at(CandidateIndex);
|
||||
p_Context->CurrentCandidateIndex = CandidateIndex;
|
||||
Dri_Nus_ResetServiceState(p_Port);
|
||||
|
||||
if (!Candidate.DeviceInfo.isValid())
|
||||
{
|
||||
p_Port->TextEndpointSummary =
|
||||
QStringLiteral("BLE candidate is invalid: %1").arg(Candidate.DeviceLabel);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!Dri_Nus_HasStableConnectIdentity(Candidate.DeviceInfo))
|
||||
{
|
||||
p_Port->TextEndpointSummary =
|
||||
QStringLiteral("BLE candidate %1 has no stable address/uuid. Skip connect.")
|
||||
.arg(Candidate.DeviceLabel);
|
||||
return false;
|
||||
}
|
||||
|
||||
p_Context->p_Controller = QLowEnergyController::createCentral(
|
||||
Candidate.DeviceInfo,
|
||||
QCoreApplication::instance());
|
||||
if (p_Context->p_Controller == nullptr)
|
||||
{
|
||||
p_Port->TextEndpointSummary =
|
||||
QStringLiteral("Failed to create BLE controller for %1").arg(Candidate.DeviceLabel);
|
||||
return false;
|
||||
}
|
||||
|
||||
const QString EndpointId = Candidate.EndpointId;
|
||||
const QString DeviceLabel = Candidate.DeviceLabel;
|
||||
p_Port->TextEndpointSummary =
|
||||
QStringLiteral("BLE connecting to target device: %1")
|
||||
.arg(Dri_Nus_FormatCandidateSummary(Candidate));
|
||||
|
||||
QObject::connect(
|
||||
p_Context->p_Controller,
|
||||
&QLowEnergyController::stateChanged,
|
||||
[p_Port, p_Context, EndpointId, DeviceLabel](QLowEnergyController::ControllerState State)
|
||||
{
|
||||
if (!Dri_Nus_IsCurrentCandidate(p_Context, EndpointId))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
p_Port->TextEndpointSummary =
|
||||
QStringLiteral("BLE controller state %1 for %2")
|
||||
.arg(static_cast<int>(State))
|
||||
.arg(DeviceLabel);
|
||||
});
|
||||
|
||||
QObject::connect(
|
||||
p_Context->p_Controller,
|
||||
&QLowEnergyController::connected,
|
||||
[p_Port, p_Context, EndpointId, DeviceLabel]()
|
||||
{
|
||||
if (!Dri_Nus_IsCurrentCandidate(p_Context, EndpointId) ||
|
||||
(p_Context->p_Controller == nullptr))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
p_Port->TextEndpointSummary =
|
||||
QStringLiteral("BLE link connected. Discovering NUS service on %1")
|
||||
.arg(DeviceLabel);
|
||||
p_Context->p_Controller->discoverServices();
|
||||
});
|
||||
|
||||
QObject::connect(
|
||||
p_Context->p_Controller,
|
||||
&QLowEnergyController::disconnected,
|
||||
[p_Port, p_Context, EndpointId, DeviceLabel]()
|
||||
{
|
||||
if (!Dri_Nus_IsCurrentCandidate(p_Context, EndpointId))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Dri_Nus_AdvanceFromCurrentCandidate(
|
||||
p_Port,
|
||||
QStringLiteral("BLE candidate disconnected: %1").arg(DeviceLabel));
|
||||
});
|
||||
|
||||
QObject::connect(
|
||||
p_Context->p_Controller,
|
||||
static_cast<void (QLowEnergyController::*)(QLowEnergyController::Error)>(
|
||||
&QLowEnergyController::error),
|
||||
[p_Port, p_Context, EndpointId, DeviceLabel](QLowEnergyController::Error)
|
||||
{
|
||||
if (!Dri_Nus_IsCurrentCandidate(p_Context, EndpointId))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Dri_Nus_AdvanceFromCurrentCandidate(
|
||||
p_Port,
|
||||
QStringLiteral("BLE candidate connect error: %1").arg(DeviceLabel));
|
||||
});
|
||||
|
||||
QObject::connect(
|
||||
p_Context->p_Controller,
|
||||
&QLowEnergyController::serviceDiscovered,
|
||||
[p_Port, p_Context, EndpointId, DeviceLabel](const QBluetoothUuid& ServiceUuid)
|
||||
{
|
||||
if (!Dri_Nus_IsCurrentCandidate(p_Context, EndpointId))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (ServiceUuid == kServiceUuid)
|
||||
{
|
||||
p_Context->HasTargetService = true;
|
||||
p_Port->TextEndpointSummary =
|
||||
QStringLiteral("BLE found NUS service on %1").arg(DeviceLabel);
|
||||
}
|
||||
});
|
||||
|
||||
QObject::connect(
|
||||
p_Context->p_Controller,
|
||||
&QLowEnergyController::discoveryFinished,
|
||||
[p_Port, p_Context, EndpointId, DeviceLabel]()
|
||||
{
|
||||
if (!Dri_Nus_IsCurrentCandidate(p_Context, EndpointId))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!p_Context->HasTargetService)
|
||||
{
|
||||
Dri_Nus_AdvanceFromCurrentCandidate(
|
||||
p_Port,
|
||||
QStringLiteral("BLE candidate %1 does not expose the NUS service.").arg(DeviceLabel));
|
||||
return;
|
||||
}
|
||||
|
||||
p_Context->p_Service =
|
||||
p_Context->p_Controller->createServiceObject(kServiceUuid, p_Context->p_Controller);
|
||||
if (p_Context->p_Service == nullptr)
|
||||
{
|
||||
Dri_Nus_AdvanceFromCurrentCandidate(
|
||||
p_Port,
|
||||
QStringLiteral("BLE candidate %1 failed to create the NUS service object.")
|
||||
.arg(DeviceLabel));
|
||||
return;
|
||||
}
|
||||
|
||||
Dri_Nus_AttachServiceSignals(
|
||||
p_Port,
|
||||
p_Context,
|
||||
EndpointId,
|
||||
DeviceLabel);
|
||||
});
|
||||
|
||||
QTimer::singleShot(
|
||||
0,
|
||||
p_Context->p_Controller,
|
||||
[p_Port, p_Context, EndpointId, DeviceLabel]()
|
||||
{
|
||||
if (!Dri_Nus_IsCurrentCandidate(p_Context, EndpointId) ||
|
||||
(p_Context->p_Controller == nullptr))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
p_Port->TextEndpointSummary =
|
||||
QStringLiteral("BLE starting GATT connect on %1").arg(DeviceLabel);
|
||||
p_Context->p_Controller->connectToDevice();
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Dri_Nus_StartNextCandidate(Dri_Nus_Struct_Port* p_Port)
|
||||
{
|
||||
Dri_Nus_Struct_Context* p_Context = p_Port->p_Context;
|
||||
if ((p_Context == nullptr) ||
|
||||
!p_Port->IsOpened ||
|
||||
!p_Context->IsDiscoveryFinished ||
|
||||
(p_Context->LockedCandidateIndex >= 0) ||
|
||||
(p_Context->p_Controller != nullptr))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int Index = p_Context->CurrentCandidateIndex + 1;
|
||||
Index < p_Context->CandidateList.size();
|
||||
++Index)
|
||||
{
|
||||
if (Dri_Nus_StartController(p_Port, p_Context, Index))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
p_Port->IsOpened = false;
|
||||
p_Port->TextEndpointSummary = p_Context->CandidateList.isEmpty()
|
||||
? QStringLiteral("No target BLE keyboard was discovered for NUS handshake.")
|
||||
: QStringLiteral("All target BLE candidates failed NUS handshake confirmation.");
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void Dri_Nus_Close(Dri_Nus_Struct_Port* p_Port)
|
||||
{
|
||||
Dri_Nus_DeleteContext(p_Port->p_Context);
|
||||
*p_Port = Dri_Nus_Struct_Port();
|
||||
}
|
||||
|
||||
bool Dri_Nus_Init(
|
||||
Dri_Nus_Struct_Port* p_Port,
|
||||
const Com_Struct_DeviceConfig& DeviceConfig,
|
||||
QString* p_TextStatus)
|
||||
{
|
||||
Dri_Nus_Close(p_Port);
|
||||
|
||||
if ((QBluetoothDeviceDiscoveryAgent::supportedDiscoveryMethods() &
|
||||
QBluetoothDeviceDiscoveryAgent::LowEnergyMethod) == 0)
|
||||
{
|
||||
p_Port->TextEndpointSummary = QStringLiteral("The current Qt platform does not support BLE.");
|
||||
if (p_TextStatus != nullptr)
|
||||
{
|
||||
*p_TextStatus = p_Port->TextEndpointSummary;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
auto* p_Context = new Dri_Nus_Struct_Context();
|
||||
p_Context->p_DiscoveryAgent = new QBluetoothDeviceDiscoveryAgent();
|
||||
p_Context->p_DiscoveryAgent->setLowEnergyDiscoveryTimeout(3000);
|
||||
|
||||
QObject::connect(
|
||||
p_Context->p_DiscoveryAgent,
|
||||
&QBluetoothDeviceDiscoveryAgent::deviceDiscovered,
|
||||
[p_Port, p_Context](const QBluetoothDeviceInfo& DeviceInfo)
|
||||
{
|
||||
if ((DeviceInfo.coreConfigurations() &
|
||||
QBluetoothDeviceInfo::LowEnergyCoreConfiguration) == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Dri_Nus_Struct_Candidate Candidate;
|
||||
Candidate.DeviceInfo = DeviceInfo;
|
||||
Candidate.DeviceName = DeviceInfo.name().trimmed();
|
||||
if (!Dri_Nus_IsPreferredDeviceName(Candidate.DeviceName))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Candidate.AddressText = Dri_Nus_NormalizeAddressText(DeviceInfo.address());
|
||||
Candidate.DeviceLabel = !Candidate.DeviceName.isEmpty()
|
||||
? Candidate.DeviceName
|
||||
: Candidate.AddressText;
|
||||
Candidate.EndpointId =
|
||||
Dri_Nus_BuildEndpointId(DeviceInfo, p_Context->CandidateList.size() + 1);
|
||||
|
||||
for (const Dri_Nus_Struct_Candidate& ExistingCandidate : p_Context->CandidateList)
|
||||
{
|
||||
if (ExistingCandidate.EndpointId.compare(
|
||||
Candidate.EndpointId,
|
||||
Qt::CaseInsensitive) == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
p_Context->CandidateList.append(Candidate);
|
||||
p_Port->TextEndpointSummary =
|
||||
QStringLiteral("BLE target candidate discovered: %1")
|
||||
.arg(Dri_Nus_FormatCandidateSummary(Candidate));
|
||||
});
|
||||
|
||||
QObject::connect(
|
||||
p_Context->p_DiscoveryAgent,
|
||||
&QBluetoothDeviceDiscoveryAgent::finished,
|
||||
[p_Port, p_Context]()
|
||||
{
|
||||
p_Context->IsDiscoveryFinished = true;
|
||||
p_Port->TextEndpointSummary = p_Context->CandidateList.isEmpty()
|
||||
? QStringLiteral("BLE scan finished, but the target keyboard name was not found.")
|
||||
: QStringLiteral("BLE scan finished. %1 target candidate(s) found. Starting handshake.")
|
||||
.arg(p_Context->CandidateList.size());
|
||||
QTimer::singleShot(
|
||||
0,
|
||||
p_Context->p_DiscoveryAgent,
|
||||
[p_Port]()
|
||||
{
|
||||
Dri_Nus_StartNextCandidate(p_Port);
|
||||
});
|
||||
});
|
||||
|
||||
QObject::connect(
|
||||
p_Context->p_DiscoveryAgent,
|
||||
static_cast<void (QBluetoothDeviceDiscoveryAgent::*)(QBluetoothDeviceDiscoveryAgent::Error)>(
|
||||
&QBluetoothDeviceDiscoveryAgent::error),
|
||||
[p_Port, p_Context](QBluetoothDeviceDiscoveryAgent::Error)
|
||||
{
|
||||
p_Context->IsDiscoveryFinished = true;
|
||||
QTimer::singleShot(
|
||||
0,
|
||||
p_Context->p_DiscoveryAgent,
|
||||
[p_Port]()
|
||||
{
|
||||
Dri_Nus_StartNextCandidate(p_Port);
|
||||
});
|
||||
});
|
||||
|
||||
p_Port->p_Context = p_Context;
|
||||
p_Port->IsOpened = true;
|
||||
p_Port->TextEndpointSummary =
|
||||
QStringLiteral("BLE scan started. Looking for the target keyboard name only.");
|
||||
p_Context->p_DiscoveryAgent->start(QBluetoothDeviceDiscoveryAgent::LowEnergyMethod);
|
||||
|
||||
if (p_TextStatus != nullptr)
|
||||
{
|
||||
*p_TextStatus = p_Port->TextEndpointSummary;
|
||||
}
|
||||
|
||||
Q_UNUSED(DeviceConfig);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Dri_Nus_Read(
|
||||
Dri_Nus_Struct_Port* p_Port,
|
||||
Com_Struct_RawPacket* p_Packet,
|
||||
QString* p_TextStatus)
|
||||
{
|
||||
*p_Packet = Com_Struct_RawPacket();
|
||||
p_Packet->Source = Com_Enum_RawPacketSource_BleNus;
|
||||
p_Packet->PortName = QStringLiteral("BLE NUS");
|
||||
|
||||
Dri_Nus_Struct_Context* p_Context = p_Port->p_Context;
|
||||
if (!p_Port->IsOpened || (p_Context == nullptr) || p_Context->PacketQueue.isEmpty())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
*p_Packet = p_Context->PacketQueue.dequeue();
|
||||
if (p_TextStatus != nullptr)
|
||||
{
|
||||
*p_TextStatus = p_Port->TextEndpointSummary;
|
||||
}
|
||||
|
||||
return p_Packet->IsValid;
|
||||
}
|
||||
|
||||
bool Dri_Nus_LockCandidate(
|
||||
Dri_Nus_Struct_Port* p_Port,
|
||||
const QString& EndpointId,
|
||||
QString* p_TextStatus)
|
||||
{
|
||||
Dri_Nus_Struct_Context* p_Context = p_Port->p_Context;
|
||||
const Dri_Nus_Struct_Candidate* p_CurrentCandidate = Dri_Nus_GetCurrentCandidate(p_Context);
|
||||
if (!p_Port->IsOpened || !p_Port->IsConnected || (p_CurrentCandidate == nullptr))
|
||||
{
|
||||
if (p_TextStatus != nullptr)
|
||||
{
|
||||
*p_TextStatus = QStringLiteral("BLE NUS does not have a candidate ready to lock yet.");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (p_CurrentCandidate->EndpointId.compare(EndpointId, Qt::CaseInsensitive) != 0)
|
||||
{
|
||||
if (p_TextStatus != nullptr)
|
||||
{
|
||||
*p_TextStatus = QStringLiteral("The current BLE candidate does not match the lock target.");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
p_Context->LockedCandidateIndex = p_Context->CurrentCandidateIndex;
|
||||
if (p_Context->p_DiscoveryAgent != nullptr)
|
||||
{
|
||||
p_Context->p_DiscoveryAgent->stop();
|
||||
}
|
||||
|
||||
p_Port->TextEndpointSummary =
|
||||
QStringLiteral("BLE NUS 宸查攣瀹氱洰鏍囪澶囷細%1").arg(p_CurrentCandidate->DeviceLabel);
|
||||
if (p_TextStatus != nullptr)
|
||||
{
|
||||
*p_TextStatus = p_Port->TextEndpointSummary;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Dri_Nus_DiscardCandidate(
|
||||
Dri_Nus_Struct_Port* p_Port,
|
||||
const QString& EndpointId,
|
||||
QString* p_TextStatus)
|
||||
{
|
||||
Dri_Nus_Struct_Context* p_Context = p_Port->p_Context;
|
||||
const Dri_Nus_Struct_Candidate* p_CurrentCandidate = Dri_Nus_GetCurrentCandidate(p_Context);
|
||||
if (!p_Port->IsOpened || (p_CurrentCandidate == nullptr))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (p_CurrentCandidate->EndpointId.compare(EndpointId, Qt::CaseInsensitive) != 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const QString DeviceLabel = p_CurrentCandidate->DeviceLabel;
|
||||
Dri_Nus_AdvanceFromCurrentCandidate(
|
||||
p_Port,
|
||||
QStringLiteral("BLE 宸蹭涪寮冩彙鎵嬩笉鍖归厤鍊欓€夛細%1").arg(DeviceLabel));
|
||||
if (p_TextStatus != nullptr)
|
||||
{
|
||||
*p_TextStatus = QStringLiteral("BLE 宸蹭涪寮冩彙鎵嬩笉鍖归厤鍊欓€夛細%1").arg(DeviceLabel);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Dri_Nus_Write(
|
||||
Dri_Nus_Struct_Port* p_Port,
|
||||
const QByteArray& PacketBody,
|
||||
QString* p_TextStatus)
|
||||
{
|
||||
Dri_Nus_Struct_Context* p_Context = p_Port->p_Context;
|
||||
const Dri_Nus_Struct_Candidate* p_CurrentCandidate = Dri_Nus_GetCurrentCandidate(p_Context);
|
||||
if (!p_Port->IsOpened ||
|
||||
(p_Context == nullptr) ||
|
||||
(p_Context->p_Service == nullptr) ||
|
||||
(p_CurrentCandidate == nullptr))
|
||||
{
|
||||
if (p_TextStatus != nullptr)
|
||||
{
|
||||
*p_TextStatus = QStringLiteral("BLE NUS is not ready yet. Skip send.");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (PacketBody.isEmpty() || !p_Context->WriteCharacteristic.isValid())
|
||||
{
|
||||
if (p_TextStatus != nullptr)
|
||||
{
|
||||
*p_TextStatus = QStringLiteral("BLE NUS write characteristic is not ready.");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto CharacteristicProperties = p_Context->WriteCharacteristic.properties();
|
||||
const bool SupportsWriteWithResponse =
|
||||
(CharacteristicProperties & QLowEnergyCharacteristic::Write) != 0;
|
||||
const bool SupportsWriteWithoutResponse =
|
||||
(CharacteristicProperties & QLowEnergyCharacteristic::WriteNoResponse) != 0;
|
||||
if (!SupportsWriteWithoutResponse && !SupportsWriteWithResponse)
|
||||
{
|
||||
if (p_TextStatus != nullptr)
|
||||
{
|
||||
*p_TextStatus = QStringLiteral("BLE NUS RX characteristic has no writable property.");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
const QLowEnergyService::WriteMode PrimaryWriteMode =
|
||||
SupportsWriteWithResponse
|
||||
? QLowEnergyService::WriteWithResponse
|
||||
: QLowEnergyService::WriteWithoutResponse;
|
||||
|
||||
p_Context->p_Service->writeCharacteristic(
|
||||
p_Context->WriteCharacteristic,
|
||||
PacketBody,
|
||||
PrimaryWriteMode);
|
||||
|
||||
if (p_TextStatus != nullptr)
|
||||
{
|
||||
const QString WriteModeText =
|
||||
(PrimaryWriteMode == QLowEnergyService::WriteWithResponse)
|
||||
? QStringLiteral("write-with-response")
|
||||
: QStringLiteral("write-without-response");
|
||||
*p_TextStatus =
|
||||
QStringLiteral("BLE NUS sent protocol packet via %1: %2")
|
||||
.arg(WriteModeText, p_CurrentCandidate->DeviceLabel);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
42
DRI/Dri_Nus.h
Normal file
42
DRI/Dri_Nus.h
Normal file
@@ -0,0 +1,42 @@
|
||||
#pragma once
|
||||
|
||||
#include "COM/Com_Def.h"
|
||||
#include <QtCore/QString>
|
||||
|
||||
struct Dri_Nus_Struct_Context;
|
||||
|
||||
struct Dri_Nus_Struct_Port
|
||||
{
|
||||
bool IsOpened = false;
|
||||
bool IsConnected = false;
|
||||
bool HasWriteAck = false;
|
||||
QString TextEndpointSummary;
|
||||
Dri_Nus_Struct_Context* p_Context = nullptr;
|
||||
};
|
||||
|
||||
void Dri_Nus_Close(Dri_Nus_Struct_Port* p_Port);
|
||||
bool Dri_Nus_Init(
|
||||
Dri_Nus_Struct_Port* p_Port,
|
||||
const Com_Struct_DeviceConfig& DeviceConfig,
|
||||
QString* p_TextStatus);
|
||||
bool Dri_Nus_Read(
|
||||
Dri_Nus_Struct_Port* p_Port,
|
||||
Com_Struct_RawPacket* p_Packet,
|
||||
QString* p_TextStatus);
|
||||
|
||||
// Lock the confirmed BLE NUS candidate after LOGIC accepts a HelloRsp.
|
||||
bool Dri_Nus_LockCandidate(
|
||||
Dri_Nus_Struct_Port* p_Port,
|
||||
const QString& EndpointId,
|
||||
QString* p_TextStatus);
|
||||
|
||||
// Drop one mismatched BLE candidate and let the driver continue scanning.
|
||||
bool Dri_Nus_DiscardCandidate(
|
||||
Dri_Nus_Struct_Port* p_Port,
|
||||
const QString& EndpointId,
|
||||
QString* p_TextStatus);
|
||||
|
||||
bool Dri_Nus_Write(
|
||||
Dri_Nus_Struct_Port* p_Port,
|
||||
const QByteArray& PacketBody,
|
||||
QString* p_TextStatus);
|
||||
@@ -1,338 +1,248 @@
|
||||
#include "DRI/Dri_Vendor.h"
|
||||
#include "DRI/Dri_Vendor.h"
|
||||
|
||||
#include <hidsdi.h>
|
||||
|
||||
#pragma comment(lib, "hid.lib")
|
||||
#include <QtCore/QStringList>
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
/* ---------- 句柄与状态小工具 ---------- */
|
||||
|
||||
void Dri_Vendor_Func_SetStatus(QString* p_TextStatus, const QString& Text)
|
||||
void Dri_Vendor_CloseHandle(HANDLE* p_Handle)
|
||||
{
|
||||
if (p_TextStatus != nullptr)
|
||||
if ((*p_Handle != nullptr) && (*p_Handle != INVALID_HANDLE_VALUE))
|
||||
{
|
||||
*p_TextStatus = Text;
|
||||
CloseHandle(*p_Handle);
|
||||
}
|
||||
*p_Handle = INVALID_HANDLE_VALUE;
|
||||
}
|
||||
|
||||
HANDLE Dri_Vendor_Func_OpenHandle(const QString& DevicePath, DWORD DesiredAccess, DWORD Flags)
|
||||
{
|
||||
return CreateFileW(
|
||||
reinterpret_cast<LPCWSTR>(DevicePath.utf16()),
|
||||
DesiredAccess,
|
||||
FILE_SHARE_READ | FILE_SHARE_WRITE,
|
||||
nullptr,
|
||||
OPEN_EXISTING,
|
||||
Flags,
|
||||
nullptr);
|
||||
}
|
||||
|
||||
bool Dri_Vendor_Func_BeginRead(Dri_Vendor_Struct_Port* p_Port, QString* p_TextStatus)
|
||||
{
|
||||
if (!p_Port->IsOpened || (p_Port->InputLength == 0))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (p_Port->IsReadPending)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
ResetEvent(p_Port->HandleEvent);
|
||||
p_Port->ReadBuffer.fill(0, p_Port->InputLength);
|
||||
p_Port->OverlappedRead = {};
|
||||
p_Port->OverlappedRead.hEvent = p_Port->HandleEvent;
|
||||
|
||||
DWORD BytesRead = 0;
|
||||
if (ReadFile(
|
||||
p_Port->HandleRead,
|
||||
p_Port->ReadBuffer.data(),
|
||||
p_Port->InputLength,
|
||||
&BytesRead,
|
||||
&p_Port->OverlappedRead) ||
|
||||
(GetLastError() == ERROR_IO_PENDING))
|
||||
{
|
||||
p_Port->IsReadPending = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
Dri_Vendor_Func_SetStatus(
|
||||
p_TextStatus,
|
||||
QStringLiteral("Vendor 接口启动异步读取失败:%1").arg(GetLastError()));
|
||||
return false;
|
||||
}
|
||||
|
||||
HANDLE Dri_Vendor_Func_OpenWriteHandle(const QString& DevicePath, QString* p_TextError)
|
||||
{
|
||||
HANDLE HandleWrite = Dri_Vendor_Func_OpenHandle(DevicePath, GENERIC_WRITE, 0);
|
||||
if (HandleWrite != INVALID_HANDLE_VALUE)
|
||||
{
|
||||
return HandleWrite;
|
||||
}
|
||||
|
||||
const DWORD WriteError = GetLastError();
|
||||
HandleWrite = Dri_Vendor_Func_OpenHandle(DevicePath, 0, 0);
|
||||
|
||||
if ((HandleWrite == INVALID_HANDLE_VALUE) && (p_TextError != nullptr))
|
||||
{
|
||||
*p_TextError = QStringLiteral("GENERIC_WRITE=%1,ZeroAccess=%2")
|
||||
.arg(WriteError)
|
||||
.arg(GetLastError());
|
||||
}
|
||||
return HandleWrite;
|
||||
}
|
||||
|
||||
bool Dri_Vendor_Func_TryWritePacket(
|
||||
bool Dri_Vendor_TryWrite(
|
||||
HANDLE HandleWrite,
|
||||
quint16 OutputLength,
|
||||
const QByteArray& Packet,
|
||||
QString* p_TextError)
|
||||
const QString& SuccessText,
|
||||
const QString& ErrorPrefix,
|
||||
QString* p_TextStatus,
|
||||
QStringList* p_ErrorList)
|
||||
{
|
||||
if ((HandleWrite == INVALID_HANDLE_VALUE) || Packet.isEmpty())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
QByteArray Buffer = Packet;
|
||||
if ((OutputLength > 0) && (Buffer.size() < OutputLength))
|
||||
{
|
||||
Buffer.append(OutputLength - Buffer.size(), 0);
|
||||
}
|
||||
|
||||
if (HidD_SetOutputReport(HandleWrite, Buffer.data(), static_cast<ULONG>(Buffer.size())))
|
||||
QString TextError;
|
||||
if (Dri_Hid_WritePacket(HandleWrite, OutputLength, Packet, &TextError))
|
||||
{
|
||||
if (p_TextStatus != nullptr)
|
||||
{
|
||||
*p_TextStatus = SuccessText;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
const DWORD SetReportError = GetLastError();
|
||||
DWORD BytesWritten = 0;
|
||||
if (WriteFile(
|
||||
HandleWrite,
|
||||
Buffer.constData(),
|
||||
static_cast<DWORD>(Buffer.size()),
|
||||
&BytesWritten,
|
||||
nullptr) &&
|
||||
(BytesWritten == static_cast<DWORD>(Buffer.size())))
|
||||
if (!TextError.isEmpty())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (p_TextError != nullptr)
|
||||
{
|
||||
*p_TextError = QStringLiteral("SetOutputReport=%1,WriteFile=%2")
|
||||
.arg(SetReportError)
|
||||
.arg(GetLastError());
|
||||
p_ErrorList->append(ErrorPrefix.arg(TextError));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
/* ---------- 生命周期 ---------- */
|
||||
|
||||
void Dri_Vendor_Func_Close(Dri_Vendor_Struct_Port* p_Port)
|
||||
void Dri_Vendor_Close(Dri_Vendor_Struct_Port* p_Port)
|
||||
{
|
||||
if ((p_Port->HandleRead != INVALID_HANDLE_VALUE) && p_Port->IsReadPending)
|
||||
{
|
||||
CancelIoEx(p_Port->HandleRead, &p_Port->OverlappedRead);
|
||||
}
|
||||
|
||||
for (HANDLE* p_Handle : { &p_Port->HandleRead, &p_Port->HandleWriteVendor, &p_Port->HandleWriteNkro, &p_Port->HandleEvent })
|
||||
{
|
||||
if ((*p_Handle != nullptr) && (*p_Handle != INVALID_HANDLE_VALUE))
|
||||
{
|
||||
CloseHandle(*p_Handle);
|
||||
}
|
||||
}
|
||||
|
||||
*p_Port = Dri_Vendor_Struct_Port();
|
||||
Dri_Hid_CloseReadPort(&p_Port->ReadPort);
|
||||
Dri_Vendor_CloseHandle(&p_Port->HandleWriteVendor);
|
||||
Dri_Vendor_CloseHandle(&p_Port->HandleWriteCommand);
|
||||
Dri_Vendor_CloseHandle(&p_Port->HandleWriteNkro);
|
||||
p_Port->VendorWriteOutputLength = 0;
|
||||
p_Port->CommandWriteOutputLength = 0;
|
||||
p_Port->NkroWriteOutputLength = 0;
|
||||
p_Port->IsBluetoothTransport = false;
|
||||
}
|
||||
|
||||
bool Dri_Vendor_Func_Open(
|
||||
bool Dri_Vendor_Init(
|
||||
Dri_Vendor_Struct_Port* p_Port,
|
||||
const Mid_Struct_DeviceConfig& DeviceConfig,
|
||||
QString* p_TextStatus)
|
||||
{
|
||||
Dri_Vendor_Func_Close(p_Port);
|
||||
Dri_Vendor_Close(p_Port);
|
||||
|
||||
Mid_Struct_DeviceMatch VendorMatch;
|
||||
VendorMatch.VendorId = DeviceConfig.VendorId;
|
||||
VendorMatch.ProductId = DeviceConfig.ProductId;
|
||||
VendorMatch.UsagePage = MID_CONST_USAGE_PAGE_VENDOR;
|
||||
VendorMatch.Usage = MID_CONST_USAGE_VENDOR;
|
||||
|
||||
QString VendorPath;
|
||||
QString VendorInstanceId;
|
||||
quint16 InputLength = 0;
|
||||
quint16 OutputLength = 0;
|
||||
if (!Mid_Func_FindHidInterface(
|
||||
Mid_Func_GetVendorMatch(DeviceConfig),
|
||||
quint16 VendorOutputLength = 0;
|
||||
if (!Mid_FindHidInterface(
|
||||
VendorMatch,
|
||||
&VendorPath,
|
||||
&InputLength,
|
||||
&OutputLength))
|
||||
&VendorOutputLength,
|
||||
&VendorInstanceId))
|
||||
{
|
||||
Dri_Vendor_Func_SetStatus(p_TextStatus, QStringLiteral("未找到 Vendor 接口:FF00 / 0002。"));
|
||||
if (p_TextStatus != nullptr)
|
||||
{
|
||||
*p_TextStatus = QStringLiteral("Vendor interface was not found: FF00 / 0002.");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
p_Port->HandleRead = Dri_Vendor_Func_OpenHandle(VendorPath, GENERIC_READ, FILE_FLAG_OVERLAPPED);
|
||||
if (p_Port->HandleRead == INVALID_HANDLE_VALUE)
|
||||
p_Port->IsBluetoothTransport = Mid_IsBluetoothHidInstanceId(VendorInstanceId);
|
||||
const QString VendorPortName = p_Port->IsBluetoothTransport
|
||||
? QStringLiteral("Bluetooth/HID Vendor")
|
||||
: QStringLiteral("Vendor");
|
||||
|
||||
if (!Dri_Hid_InitReadPort(
|
||||
&p_Port->ReadPort,
|
||||
VendorPath,
|
||||
InputLength,
|
||||
Mid_Enum_RawPacketSource_UsbVendorHid,
|
||||
VendorPortName,
|
||||
p_TextStatus))
|
||||
{
|
||||
Dri_Vendor_Func_SetStatus(
|
||||
p_TextStatus,
|
||||
QStringLiteral("Vendor 接口打开读句柄失败:%1").arg(GetLastError()));
|
||||
return false;
|
||||
}
|
||||
|
||||
p_Port->HandleEvent = CreateEventW(nullptr, TRUE, FALSE, nullptr);
|
||||
if (p_Port->HandleEvent == nullptr)
|
||||
{
|
||||
Dri_Vendor_Func_SetStatus(
|
||||
p_TextStatus,
|
||||
QStringLiteral("Vendor 接口创建事件失败:%1").arg(GetLastError()));
|
||||
Dri_Vendor_Func_Close(p_Port);
|
||||
return false;
|
||||
}
|
||||
p_Port->HandleWriteVendor = Dri_Hid_OpenWriteHandle(VendorPath, nullptr);
|
||||
p_Port->VendorWriteOutputLength = VendorOutputLength;
|
||||
|
||||
p_Port->HandleWriteVendor = Dri_Vendor_Func_OpenWriteHandle(VendorPath, nullptr);
|
||||
QString CommandPath;
|
||||
quint16 CommandOutputLength = 0;
|
||||
Mid_Struct_DeviceMatch CommandMatch;
|
||||
CommandMatch.VendorId = DeviceConfig.VendorId;
|
||||
CommandMatch.ProductId = DeviceConfig.ProductId;
|
||||
CommandMatch.UsagePage = MID_CONST_USAGE_PAGE_VENDOR_COMMAND;
|
||||
CommandMatch.Usage = MID_CONST_USAGE_VENDOR_COMMAND;
|
||||
if (Mid_FindHidInterface(
|
||||
CommandMatch,
|
||||
&CommandPath,
|
||||
nullptr,
|
||||
&CommandOutputLength))
|
||||
{
|
||||
QString TextError;
|
||||
p_Port->HandleWriteCommand = Dri_Hid_OpenWriteHandle(CommandPath, &TextError);
|
||||
p_Port->CommandWriteOutputLength = CommandOutputLength;
|
||||
if ((p_Port->HandleWriteCommand == INVALID_HANDLE_VALUE) && (p_TextStatus != nullptr))
|
||||
{
|
||||
*p_TextStatus = QStringLiteral("Vendor command write handle failed: %1").arg(TextError);
|
||||
}
|
||||
}
|
||||
else if (p_TextStatus != nullptr)
|
||||
{
|
||||
*p_TextStatus = QStringLiteral("Vendor command interface was not found: FF01 / 0005.");
|
||||
}
|
||||
|
||||
QString NkroPath;
|
||||
quint16 NkroOutputLength = 0;
|
||||
if (Mid_Func_FindHidInterface(
|
||||
Mid_Func_GetNkroMatch(DeviceConfig),
|
||||
Mid_Struct_DeviceMatch NkroMatch;
|
||||
NkroMatch.VendorId = DeviceConfig.VendorId;
|
||||
NkroMatch.ProductId = DeviceConfig.ProductId;
|
||||
NkroMatch.UsagePage = MID_CONST_USAGE_PAGE_NKRO;
|
||||
NkroMatch.Usage = MID_CONST_USAGE_NKRO;
|
||||
if (Mid_FindHidInterface(
|
||||
NkroMatch,
|
||||
&NkroPath,
|
||||
nullptr,
|
||||
&NkroOutputLength))
|
||||
{
|
||||
QString TextError;
|
||||
p_Port->HandleWriteNkro = Dri_Vendor_Func_OpenWriteHandle(NkroPath, &TextError);
|
||||
p_Port->HandleWriteNkro = Dri_Hid_OpenWriteHandle(NkroPath, &TextError);
|
||||
p_Port->NkroWriteOutputLength = NkroOutputLength;
|
||||
|
||||
if (p_Port->HandleWriteNkro == INVALID_HANDLE_VALUE)
|
||||
if ((p_Port->HandleWriteNkro == INVALID_HANDLE_VALUE) && (p_TextStatus != nullptr))
|
||||
{
|
||||
Dri_Vendor_Func_SetStatus(
|
||||
p_TextStatus,
|
||||
QStringLiteral("Vendor 读链路已打开,但 NKRO 写句柄打开失败:%1").arg(TextError));
|
||||
*p_TextStatus = QStringLiteral("NKRO write handle failed: %1").arg(TextError);
|
||||
}
|
||||
}
|
||||
else
|
||||
else if (p_TextStatus != nullptr)
|
||||
{
|
||||
Dri_Vendor_Func_SetStatus(
|
||||
p_TextStatus,
|
||||
QStringLiteral("Vendor 读链路已打开,但没有找到 NKRO 写接口。"));
|
||||
*p_TextStatus = QStringLiteral("NKRO write interface was not found.");
|
||||
}
|
||||
|
||||
p_Port->InputLength = InputLength;
|
||||
p_Port->OutputLength = OutputLength;
|
||||
p_Port->ReadBuffer = QByteArray(InputLength, 0);
|
||||
p_Port->OverlappedRead.hEvent = p_Port->HandleEvent;
|
||||
p_Port->IsOpened = true;
|
||||
|
||||
if (!Dri_Vendor_Func_BeginRead(p_Port, p_TextStatus))
|
||||
{
|
||||
Dri_Vendor_Func_Close(p_Port);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/* ---------- 读写流程 ---------- */
|
||||
|
||||
bool Dri_Vendor_Func_Read(
|
||||
bool Dri_Vendor_Read(
|
||||
Dri_Vendor_Struct_Port* p_Port,
|
||||
Mid_Struct_RawPacket* p_Packet,
|
||||
QString* p_TextStatus)
|
||||
{
|
||||
*p_Packet = Mid_Struct_RawPacket();
|
||||
p_Packet->PortName = QStringLiteral("Vendor");
|
||||
|
||||
if (!p_Port->IsOpened)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!p_Port->IsReadPending)
|
||||
{
|
||||
Dri_Vendor_Func_BeginRead(p_Port, p_TextStatus);
|
||||
return false;
|
||||
}
|
||||
|
||||
DWORD BytesRead = 0;
|
||||
if (!GetOverlappedResult(p_Port->HandleRead, &p_Port->OverlappedRead, &BytesRead, FALSE))
|
||||
{
|
||||
const DWORD ErrorCode = GetLastError();
|
||||
if (ErrorCode == ERROR_IO_INCOMPLETE)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
Dri_Vendor_Func_SetStatus(
|
||||
p_TextStatus,
|
||||
QStringLiteral("Vendor 接口读包失败:%1").arg(ErrorCode));
|
||||
Dri_Vendor_Func_Close(p_Port);
|
||||
return false;
|
||||
}
|
||||
|
||||
p_Port->IsReadPending = false;
|
||||
if (BytesRead > 0)
|
||||
{
|
||||
p_Packet->IsValid = true;
|
||||
p_Packet->ByteArray = p_Port->ReadBuffer.left(static_cast<int>(BytesRead));
|
||||
}
|
||||
|
||||
Dri_Vendor_Func_BeginRead(p_Port, p_TextStatus);
|
||||
return p_Packet->IsValid;
|
||||
return Dri_Hid_Read(&p_Port->ReadPort, p_Packet, p_TextStatus);
|
||||
}
|
||||
|
||||
bool Dri_Vendor_Func_Write(
|
||||
bool Dri_Vendor_Write(
|
||||
Dri_Vendor_Struct_Port* p_Port,
|
||||
const QByteArray& ByteArray,
|
||||
QString* p_TextStatus)
|
||||
{
|
||||
if (!p_Port->IsOpened)
|
||||
const QString RouteLabel = p_Port->IsBluetoothTransport
|
||||
? QStringLiteral("Bluetooth Vendor")
|
||||
: QStringLiteral("Vendor");
|
||||
|
||||
if (!p_Port->ReadPort.IsOpened)
|
||||
{
|
||||
Dri_Vendor_Func_SetStatus(
|
||||
p_TextStatus,
|
||||
QStringLiteral("Vendor 读链路未打开,不能发送掩码。"));
|
||||
if (p_TextStatus != nullptr)
|
||||
{
|
||||
*p_TextStatus = QStringLiteral("%1 read path is not open, packet was not sent.").arg(RouteLabel);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if (ByteArray.isEmpty())
|
||||
{
|
||||
if (p_TextStatus != nullptr)
|
||||
{
|
||||
*p_TextStatus = QStringLiteral("%1 packet is empty.").arg(RouteLabel);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
QStringList Errors;
|
||||
const auto TryWriteTo = [&](HANDLE HandleWrite,
|
||||
quint16 OutputLength,
|
||||
const QString& SuccessText,
|
||||
const QString& ErrorPrefix)
|
||||
QStringList ErrorList;
|
||||
const quint8 ReportId = static_cast<quint8>(ByteArray.at(0));
|
||||
|
||||
if (ReportId == Mid_Enum_ReportId_VendorCommand)
|
||||
{
|
||||
QString TextError;
|
||||
if (Dri_Vendor_Func_TryWritePacket(HandleWrite, OutputLength, ByteArray, &TextError))
|
||||
if (Dri_Vendor_TryWrite(
|
||||
p_Port->HandleWriteCommand,
|
||||
p_Port->CommandWriteOutputLength,
|
||||
ByteArray,
|
||||
QStringLiteral("Packet sent to %1 command interface.").arg(RouteLabel),
|
||||
RouteLabel + QStringLiteral(" command write failed: %1"),
|
||||
p_TextStatus,
|
||||
&ErrorList))
|
||||
{
|
||||
Dri_Vendor_Func_SetStatus(p_TextStatus, SuccessText);
|
||||
return true;
|
||||
}
|
||||
if (!TextError.isEmpty())
|
||||
}
|
||||
else
|
||||
{
|
||||
if (Dri_Vendor_TryWrite(
|
||||
p_Port->HandleWriteNkro,
|
||||
p_Port->NkroWriteOutputLength,
|
||||
ByteArray,
|
||||
QStringLiteral("Packet sent to NKRO write interface."),
|
||||
QStringLiteral("NKRO write failed: %1"),
|
||||
p_TextStatus,
|
||||
&ErrorList))
|
||||
{
|
||||
Errors.append(ErrorPrefix.arg(TextError));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
if (TryWriteTo(
|
||||
p_Port->HandleWriteNkro,
|
||||
p_Port->NkroWriteOutputLength,
|
||||
QStringLiteral("掩码已发送到 NKRO 写接口。"),
|
||||
QStringLiteral("NKRO 写接口发送失败:%1")))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if (TryWriteTo(
|
||||
p_Port->HandleWriteVendor,
|
||||
p_Port->OutputLength,
|
||||
QStringLiteral("掩码已发送到 Vendor 写接口。"),
|
||||
QStringLiteral("Vendor 写接口发送失败:%1")))
|
||||
{
|
||||
return true;
|
||||
if (Dri_Vendor_TryWrite(
|
||||
p_Port->HandleWriteVendor,
|
||||
p_Port->VendorWriteOutputLength,
|
||||
ByteArray,
|
||||
QStringLiteral("Packet sent to %1 write interface.").arg(RouteLabel),
|
||||
RouteLabel + QStringLiteral(" write failed: %1"),
|
||||
p_TextStatus,
|
||||
&ErrorList))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
Dri_Vendor_Func_SetStatus(
|
||||
p_TextStatus,
|
||||
Errors.isEmpty()
|
||||
? QStringLiteral("没有可用的写句柄,掩码发送未执行。")
|
||||
: Errors.join(QStringLiteral("\n")));
|
||||
if (p_TextStatus != nullptr)
|
||||
{
|
||||
*p_TextStatus = ErrorList.isEmpty()
|
||||
? QStringLiteral("No writable output handle is available, packet send was skipped.")
|
||||
: ErrorList.join(QStringLiteral("\n"));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,42 +1,31 @@
|
||||
#pragma once
|
||||
#pragma once
|
||||
|
||||
#include "MID/Mid_Def.h"
|
||||
#include <QtCore/QByteArray>
|
||||
#include <QtCore/QString>
|
||||
#include <Windows.h>
|
||||
#include "DRI/Dri_Hid.h"
|
||||
|
||||
/*
|
||||
* DRI Vendor 层:读写固件的 Vendor/NKRO HID 接口,支撑逻辑层功能诊断。
|
||||
*
|
||||
*/
|
||||
// USB vendor reader plus three write routes: vendor, command, and NKRO.
|
||||
struct Dri_Vendor_Struct_Port
|
||||
{
|
||||
/* 读写句柄:Vendor 写、NKRO 写与通用读各占一个 */
|
||||
HANDLE HandleRead = INVALID_HANDLE_VALUE;
|
||||
Dri_Hid_Struct_ReadPort ReadPort;
|
||||
HANDLE HandleWriteVendor = INVALID_HANDLE_VALUE;
|
||||
HANDLE HandleWriteCommand = INVALID_HANDLE_VALUE;
|
||||
HANDLE HandleWriteNkro = INVALID_HANDLE_VALUE;
|
||||
HANDLE HandleEvent = nullptr;
|
||||
OVERLAPPED OverlappedRead = {};
|
||||
/* 运行状态 + 报文长度缓存,便于 UI/LOGIC 层快速判断 */
|
||||
bool IsOpened = false;
|
||||
bool IsReadPending = false;
|
||||
quint16 InputLength = 0;
|
||||
quint16 OutputLength = 0;
|
||||
quint16 VendorWriteOutputLength = 0;
|
||||
quint16 CommandWriteOutputLength = 0;
|
||||
quint16 NkroWriteOutputLength = 0;
|
||||
QByteArray ReadBuffer;
|
||||
bool IsBluetoothTransport = false;
|
||||
};
|
||||
|
||||
/* 关闭全部句柄,安全退出时务必调用以便复用设备。 */
|
||||
void Dri_Vendor_Func_Close(Dri_Vendor_Struct_Port* p_Port);
|
||||
/* 根据 VID/PID 打开对应 HID 接口,并创建必需句柄。 */
|
||||
bool Dri_Vendor_Func_Open(Dri_Vendor_Struct_Port* p_Port,
|
||||
void Dri_Vendor_Close(Dri_Vendor_Struct_Port* p_Port);
|
||||
bool Dri_Vendor_Init(
|
||||
Dri_Vendor_Struct_Port* p_Port,
|
||||
const Mid_Struct_DeviceConfig& DeviceConfig,
|
||||
QString* p_TextStatus);
|
||||
/* 读取 Vendor 报文:一次返回一个 Mid_Struct_RawPacket。 */
|
||||
bool Dri_Vendor_Func_Read(Dri_Vendor_Struct_Port* p_Port,
|
||||
bool Dri_Vendor_Read(
|
||||
Dri_Vendor_Struct_Port* p_Port,
|
||||
Mid_Struct_RawPacket* p_Packet,
|
||||
QString* p_TextStatus);
|
||||
/* 写入 Vendor/NKRO 报文,ByteArray 按固件定义组包。 */
|
||||
bool Dri_Vendor_Func_Write(Dri_Vendor_Struct_Port* p_Port,
|
||||
bool Dri_Vendor_Write(
|
||||
Dri_Vendor_Struct_Port* p_Port,
|
||||
const QByteArray& ByteArray,
|
||||
QString* p_TextStatus);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user