921 lines
29 KiB
C++
921 lines
29 KiB
C++
|
|
#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;
|
|||
|
|
}
|