Files
0417_QT_code/DRI/Dri_Nus.cpp

921 lines
29 KiB
C++
Raw Permalink Normal View History

2026-04-17 16:25:19 +08:00
#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;
}