414 lines
14 KiB
C++
414 lines
14 KiB
C++
#include "qbluetoothleuartclient.h"
|
|
|
|
QBluetoothLeUartClient::QBluetoothLeUartClient(QObject *parent) : QObject(parent)
|
|
{
|
|
currentBluetoothDevice = nullptr;
|
|
bluetoothController = nullptr;
|
|
bluetoothService = nullptr;
|
|
|
|
state = Idle;
|
|
|
|
this->setUUIDs("6e400001-b5a3-f393-e0a9-e50e24dcca9e", "6e400002-b5a3-f393-e0a9-e50e24dcca9e", "6e400003-b5a3-f393-e0a9-e50e24dcca9e");
|
|
|
|
// init device discovery agent for scanning
|
|
this->bluetoothDeviceDiscoveryAgent = new QBluetoothDeviceDiscoveryAgent(this);
|
|
|
|
connect(this->bluetoothDeviceDiscoveryAgent, &QBluetoothDeviceDiscoveryAgent::deviceDiscovered, this, &QBluetoothLeUartClient::handleDeviceDiscovered);
|
|
connect(bluetoothDeviceDiscoveryAgent, SIGNAL(error(QBluetoothDeviceDiscoveryAgent::Error)),
|
|
this, SLOT(handleDeviceScanError(QBluetoothDeviceDiscoveryAgent::Error)));
|
|
connect(this->bluetoothDeviceDiscoveryAgent, &QBluetoothDeviceDiscoveryAgent::finished, this, &QBluetoothLeUartClient::handleScanFinished);
|
|
|
|
// device model for QML
|
|
this->availableDevicesModel = new QBluetoothLeUartDeviceModel(this->availableDevices, this);
|
|
}
|
|
|
|
QBluetoothLeUartClient::~QBluetoothLeUartClient(){
|
|
|
|
}
|
|
|
|
// ------------------------------
|
|
// - Slots for QBluetoothLeUart -
|
|
// ------------------------------
|
|
|
|
bool QBluetoothLeUartClient::startScanningForDevices(){
|
|
if(this->state != Idle && this->state != AdapterTurnedOff && this->state != ScanFinished && this->state != LocationPermissionDenied)
|
|
return false;
|
|
#ifdef Q_OS_ANDROID
|
|
else if(this->state == LocationPermissionDenied) {
|
|
// try to get permission
|
|
QtAndroid::PermissionResultMap resultMap = QtAndroid::requestPermissionsSync({"android.permission.ACCESS_FINE_LOCATION", "android.permission.ACCESS_COARSE_LOCATION"});
|
|
bool resultBool = true;
|
|
for(QtAndroid::PermissionResult result : resultMap) {
|
|
if(result != QtAndroid::PermissionResult::Granted) {
|
|
resultBool = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if(!resultBool) {
|
|
emit this->scanningErrorOccured(LocationPermissionDeniedError);
|
|
return false;
|
|
}
|
|
|
|
}
|
|
#endif
|
|
|
|
this->availableDevicesModel->clear();
|
|
|
|
foreach(QBluetoothLeUartDevice* oldDevice, this->availableDevices)
|
|
oldDevice->deleteLater();
|
|
|
|
this->availableDevices.clear();
|
|
|
|
this->setState(Scanning);
|
|
this->bluetoothDeviceDiscoveryAgent->start(QBluetoothDeviceDiscoveryAgent::LowEnergyMethod);
|
|
|
|
return true;
|
|
}
|
|
|
|
bool QBluetoothLeUartClient::stopScanningForDevices() {
|
|
if(this->state != Scanning)
|
|
return false;
|
|
|
|
this->bluetoothDeviceDiscoveryAgent->stop();
|
|
this->setState(ScanFinished);
|
|
return true;
|
|
}
|
|
|
|
QList<QBluetoothLeUartDevice*> QBluetoothLeUartClient::getAvailableDevices() {
|
|
return this->availableDevices;
|
|
}
|
|
|
|
QVariantList QBluetoothLeUartClient::getAvailableDevicesDetailList() {
|
|
|
|
QVariantList result;
|
|
|
|
for(int i=0; i < this->availableDevices.length(); i++) {
|
|
if(this->availableDevices[i]->getName().isEmpty())
|
|
continue;
|
|
|
|
QVariantMap device;
|
|
|
|
device.insert("id", i);
|
|
device.insert("name", this->availableDevices[i]->getName());
|
|
device.insert("address", this->availableDevices[i]->getAddress());
|
|
|
|
result.append(device);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
QBluetoothLeUartDeviceModel* QBluetoothLeUartClient::getAvailableDevicesModel() {
|
|
return this->availableDevicesModel;
|
|
}
|
|
|
|
QBluetoothLeUartDevice* QBluetoothLeUartClient::getCurrentDevice() {
|
|
return this->currentBluetoothDevice;
|
|
}
|
|
|
|
bool QBluetoothLeUartClient::connectToDevice(int deviceId) {
|
|
if(deviceId < 0 || deviceId >= this->availableDevices.length())
|
|
return false;
|
|
|
|
this->connectToDevice(this->availableDevices[deviceId]);
|
|
return true;
|
|
}
|
|
|
|
bool QBluetoothLeUartClient::connectToDevice(QBluetoothLeUartDevice *device){
|
|
if(!this->availableDevices.contains(device))
|
|
return false;
|
|
|
|
if(this->state == Scanning)
|
|
this->stopScanningForDevices();
|
|
|
|
this->currentBluetoothDevice = device;
|
|
emit this->currentDeviceChanged();
|
|
|
|
if (bluetoothController) {
|
|
bluetoothController->disconnectFromDevice();
|
|
delete bluetoothController;
|
|
bluetoothController = 0;
|
|
}
|
|
|
|
// initialize QLowEnergyController
|
|
bluetoothController = new QLowEnergyController(currentBluetoothDevice->getDevice(), this);
|
|
bluetoothController->setRemoteAddressType(QLowEnergyController::RandomAddress);
|
|
|
|
connect(this->bluetoothController, &QLowEnergyController::serviceDiscovered, this, &QBluetoothLeUartClient::handleServiceDiscovered);
|
|
connect(this->bluetoothController, &QLowEnergyController::discoveryFinished, this, &QBluetoothLeUartClient::handleServiceScanDone);
|
|
connect(bluetoothController, SIGNAL(error(QLowEnergyController::Error)),
|
|
this, SLOT(handleControllerError(QLowEnergyController::Error)));
|
|
connect(this->bluetoothController, &QLowEnergyController::connected, this, &QBluetoothLeUartClient::handleDeviceConnected);
|
|
connect(this->bluetoothController, &QLowEnergyController::disconnected, this, &QBluetoothLeUartClient::handleDeviceDisconnected);
|
|
|
|
/* Start connecting to device */
|
|
bluetoothController->connectToDevice();
|
|
setState(Connecting);
|
|
|
|
return true;
|
|
}
|
|
|
|
bool QBluetoothLeUartClient::disconnectFromDevice() {
|
|
if(this->state < Connecting)
|
|
return false;
|
|
|
|
if(this->state >= Connected)
|
|
this->bluetoothController->disconnectFromDevice();
|
|
|
|
this->bluetoothController->deleteLater();
|
|
this->bluetoothController = nullptr;
|
|
|
|
if(this->bluetoothService != nullptr) {
|
|
this->bluetoothService->deleteLater();
|
|
this->bluetoothService = nullptr;
|
|
}
|
|
|
|
this->currentBluetoothDevice->deleteLater();
|
|
this->currentBluetoothDevice = nullptr;
|
|
emit this->currentDeviceChanged();
|
|
|
|
this->setState(Idle);
|
|
|
|
return true;
|
|
}
|
|
|
|
bool QBluetoothLeUartClient::sendData(QString data, bool asynchronous){
|
|
Q_UNUSED(asynchronous)
|
|
if(this->state != Connected)
|
|
return false;
|
|
|
|
const QLowEnergyCharacteristic RxChar = bluetoothService->characteristic(QBluetoothUuid(QUuid(this->txUUID)));
|
|
|
|
bluetoothService->writeCharacteristic(RxChar, data.toUtf8(), QLowEnergyService::WriteWithoutResponse);
|
|
|
|
return true;
|
|
}
|
|
|
|
// -------------------------------------------
|
|
// - Slots for QBluetothDeviceDiscoveryAgent -
|
|
// -------------------------------------------
|
|
|
|
void QBluetoothLeUartClient::handleDeviceDiscovered(const QBluetoothDeviceInfo &device)
|
|
{
|
|
// Is it a BLE device?
|
|
if (device.coreConfigurations() & QBluetoothDeviceInfo::LowEnergyCoreConfiguration) {
|
|
//qWarning() << "Discovered BLE Device: name: " << device.name() << " Address: " << device.address().toString() << " UUIDs: " << device.serviceUuids();
|
|
// ignore all devices that to not support our service
|
|
if(!device.serviceUuids().contains(QBluetoothUuid(this->uartServiceUUID)))
|
|
return;
|
|
|
|
QBluetoothLeUartDevice *dev = new QBluetoothLeUartDevice(device, this);
|
|
this->availableDevices.append(dev);
|
|
this->availableDevicesModel->append(dev);
|
|
emit this->foundNewDevice(dev);
|
|
emit this->avaliableDevicesChanged(this->availableDevices);
|
|
}
|
|
}
|
|
|
|
void QBluetoothLeUartClient::handleScanFinished()
|
|
{
|
|
if (this->availableDevices.size() == 0)
|
|
{
|
|
qWarning() << "No Low Energy devices found";
|
|
}
|
|
|
|
emit this->scanFinished(this->availableDevices);
|
|
|
|
setState(ScanFinished);
|
|
}
|
|
|
|
void QBluetoothLeUartClient::handleDeviceScanError(QBluetoothDeviceDiscoveryAgent::Error error)
|
|
{
|
|
qWarning() << "Scanning ERROR: " << error;
|
|
|
|
this->availableDevices.clear();
|
|
this->availableDevicesModel->clear();
|
|
|
|
if (error == QBluetoothDeviceDiscoveryAgent::PoweredOffError) {
|
|
this->setState(AdapterTurnedOff);
|
|
emit this->scanningErrorOccured(AdapterTurnedOffError);
|
|
}
|
|
else if (error == QBluetoothDeviceDiscoveryAgent::InputOutputError) {
|
|
this->setState(AdapterTurnedOff);
|
|
emit this->scanningErrorOccured(InputOutputError);
|
|
}
|
|
#ifdef Q_OS_ANDROID
|
|
else if (error == QBluetoothDeviceDiscoveryAgent::UnknownError) {
|
|
// check for permission error
|
|
QtAndroid::PermissionResult fineLocationAccess = QtAndroid::checkPermission("android.permission.ACCESS_FINE_LOCATION");
|
|
QtAndroid::PermissionResult coarseLocationAccess = QtAndroid::checkPermission("android.permission.ACCESS_COARSE_LOCATION");
|
|
|
|
if(fineLocationAccess != QtAndroid::PermissionResult::Granted || coarseLocationAccess != QtAndroid::PermissionResult::Granted) {
|
|
this->setState(LocationPermissionDenied);
|
|
emit this->scanningErrorOccured(LocationPermissionDeniedError);
|
|
}
|
|
else
|
|
emit this->scanningErrorOccured(UnknownError);
|
|
}
|
|
#endif
|
|
else
|
|
emit this->scanningErrorOccured(UnknownError);
|
|
|
|
this->stopScanningForDevices();
|
|
}
|
|
|
|
// ----------------------------------
|
|
// - Slots for QLowEnergyController -
|
|
// ----------------------------------
|
|
|
|
void QBluetoothLeUartClient::handleServiceDiscovered(const QBluetoothUuid &uuid){
|
|
|
|
//qDebug() << "Found service with ID: " << uuid;
|
|
|
|
if(uuid == QBluetoothUuid(QUuid(this->uartServiceUUID))){
|
|
foundValidUARTService =true;
|
|
//qDebug() << "UART service found!";
|
|
}
|
|
}
|
|
|
|
void QBluetoothLeUartClient::handleServiceScanDone(){
|
|
|
|
delete bluetoothService;
|
|
bluetoothService=0;
|
|
|
|
if(foundValidUARTService){
|
|
//qDebug() << "Connecting to UART service...";
|
|
bluetoothService = bluetoothController->createServiceObject(QBluetoothUuid(QUuid(this->uartServiceUUID)),this);
|
|
}
|
|
|
|
if(!bluetoothService){
|
|
//qDebug() <<"UART service not found";
|
|
this->disconnectFromDevice();
|
|
return;
|
|
}
|
|
|
|
/* 3 Step: Service Discovery */
|
|
connect(this->bluetoothService, &QLowEnergyService::stateChanged, this, &QBluetoothLeUartClient::handleServiceStateChange);
|
|
connect(this->bluetoothService, &QLowEnergyService::characteristicChanged, this, &QBluetoothLeUartClient::handleServiceCharacteristicChange);
|
|
connect(this->bluetoothService, &QLowEnergyService::descriptorWritten, this, &QBluetoothLeUartClient::handleServiceDescriptorWritten);
|
|
|
|
bluetoothService->discoverDetails();
|
|
setState(ServiceFound);
|
|
}
|
|
|
|
void QBluetoothLeUartClient::handleControllerError(QLowEnergyController::Error error)
|
|
{
|
|
qDebug() << "Cannot connect to remote device.";
|
|
qWarning() << "Controller Error:" << error;
|
|
this->disconnectFromDevice();
|
|
}
|
|
|
|
void QBluetoothLeUartClient::handleDeviceConnected()
|
|
{
|
|
//qDebug() << "Device connected";
|
|
bluetoothController->discoverServices();
|
|
setState(ScanningForService);
|
|
}
|
|
|
|
void QBluetoothLeUartClient::handleDeviceDisconnected()
|
|
{
|
|
this->setState(Idle);
|
|
//qDebug() << "UART service disconnected";
|
|
qWarning() << "Remote device disconnected";
|
|
}
|
|
|
|
// -------------------------------
|
|
// - Slots for QLowEnergyService -
|
|
// -------------------------------
|
|
|
|
void QBluetoothLeUartClient::handleServiceStateChange(QLowEnergyService::ServiceState s)
|
|
{
|
|
|
|
// A descriptoc can only be written if the service is in the ServiceDiscovered state
|
|
switch (s) {
|
|
case QLowEnergyService::ServiceDiscovered:
|
|
{
|
|
|
|
//looking for the RX characteristic
|
|
const QLowEnergyCharacteristic TxChar = bluetoothService->characteristic(QBluetoothUuid(QUuid(this->rxUUID)));
|
|
if (!TxChar.isValid()){
|
|
//qDebug() << "Rx characteristic not found";
|
|
this->disconnectFromDevice();
|
|
return;
|
|
}
|
|
|
|
//looking for the TX characteristic
|
|
const QLowEnergyCharacteristic RxChar = bluetoothService->characteristic(QBluetoothUuid(QUuid(this->txUUID)));
|
|
if (!RxChar.isValid()) {
|
|
//qDebug() << "Tx characteristic not found";
|
|
this->disconnectFromDevice();
|
|
return;
|
|
}
|
|
|
|
|
|
// Bluetooth LE spec Where a characteristic can be notified, a Client Characteristic Configuration descriptor
|
|
// shall be included in that characteristic as required by the Bluetooth Core Specification
|
|
// Tx notify is enabled
|
|
const QLowEnergyDescriptor m_notificationDescTx = TxChar.descriptor(QBluetoothUuid::ClientCharacteristicConfiguration);
|
|
|
|
if (m_notificationDescTx.isValid()) {
|
|
// enable notification
|
|
bluetoothService->writeDescriptor(m_notificationDescTx, QByteArray::fromHex("0100"));
|
|
setState(Connected);
|
|
emit this->connectedToDevice();
|
|
}
|
|
|
|
break;
|
|
}
|
|
default:
|
|
//nothing for now
|
|
break;
|
|
}
|
|
}
|
|
|
|
void QBluetoothLeUartClient::handleServiceCharacteristicChange(const QLowEnergyCharacteristic &c,const QByteArray &value)
|
|
{
|
|
// ignore any other characteristic change
|
|
if (c.uuid() != QBluetoothUuid(QUuid(this->rxUUID)))
|
|
return;
|
|
|
|
emit dataReceived((QString) value);
|
|
}
|
|
|
|
void QBluetoothLeUartClient::handleServiceDescriptorWritten(const QLowEnergyDescriptor &d,
|
|
const QByteArray &value)
|
|
{
|
|
if (d.isValid() && d == bluetoothTransmissionDescriptor && value == QByteArray("0000")) {
|
|
//disabled notifications -> assume disconnect intent
|
|
this->disconnectFromDevice();
|
|
}
|
|
}
|
|
|
|
// --------------------
|
|
// - Helper functions -
|
|
// --------------------
|
|
|
|
void QBluetoothLeUartClient::init() {
|
|
#ifdef QBluetoothLeUart_QML
|
|
qmlRegisterUncreatableType<QBluetoothLeUartDevice>("de.itsblue.bluetoothleuart", 1, 0, "QBluetoothLeUartDevice", "QBluetoothLeUartDevice cannot be created");
|
|
qmlRegisterUncreatableType<QBluetoothLeUartDeviceModel>("de.itsblue.bluetoothleuart", 1, 0, "QBluetoothLeUartDeviceModel", "QBluetoothLeUartDeviceModel cannot be created");
|
|
qmlRegisterType<QBluetoothLeUartClient>("de.itsblue.bluetoothleuart", 1, 0, "QBluetoothLeUart");
|
|
qRegisterMetaType<QBluetoothLeUartClient::BluetoothLeUartClientState>("QBluetoothLeUart::BluetoothLeUartState");
|
|
qRegisterMetaType<QBluetoothLeUartClient::BluetoothScanError>("QBluetoothLeUart::BluetoothScanError");
|
|
#endif
|
|
}
|
|
|
|
void QBluetoothLeUartClient::setState(QBluetoothLeUartClient::BluetoothLeUartClientState newState)
|
|
{
|
|
if (state == newState)
|
|
return;
|
|
|
|
state = newState;
|
|
emit stateChanged(newState);
|
|
}
|
|
|
|
QBluetoothLeUartClient::BluetoothLeUartClientState QBluetoothLeUartClient::getState() const {
|
|
return state;
|
|
}
|
|
|
|
void QBluetoothLeUartClient::setUUIDs(const char uartServiceUUID[36], const char txUUID[36], const char rxUUID[36]) {
|
|
this->uartServiceUUID = uartServiceUUID;
|
|
this->txUUID = txUUID;
|
|
this->rxUUID = rxUUID;
|
|
}
|