#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::requestLocationPermission() { if(this->isLocationPermissionGranted()) return true; #ifdef Q_OS_ANDROID // 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; } } if(resultBool) { if(this->state == LocationPermissionDenied) this->setState(Idle); return true; } // getting permission the traditional way failed -> open the settings app QAndroidJniObject activity = QAndroidJniObject::callStaticObjectMethod("org/qtproject/qt5/android/QtNative", "activity", "()Landroid/app/Activity;"); //activity is valid if (activity.isValid()) { // get the package name QAndroidJniObject context = QtAndroid::androidContext(); QAndroidJniObject applicationPackageName = context.callObjectMethod("getPackageName"); QAndroidJniObject param = QAndroidJniObject::fromString("package:" + applicationPackageName.toString()); // Equivalent to Jave code: 'Uri uri = Uri::parse("...");' QAndroidJniObject uri = QAndroidJniObject::callStaticObjectMethod("android/net/Uri", "parse", "(Ljava/lang/String;)Landroid/net/Uri;", param.object()); if (!uri.isValid()) { qWarning("ERROR: Unable to create Uri object"); return false; } QAndroidJniObject packageName = QAndroidJniObject::fromString("android.settings.APPLICATION_DETAILS_SETTINGS"); QAndroidJniObject intent("android/content/Intent","(Ljava/lang/String;)V", packageName.object()); if (!intent.isValid()) { qWarning("ERROR: Unable to create Intent object"); return false; } intent.callObjectMethod("addCategory", "(Ljava/lang/String;)Landroid/content/Intent;", QAndroidJniObject::fromString("android.intent.category.DEFAULT").object()); intent.callObjectMethod("setData", "(Landroid/net/Uri;)Landroid/content/Intent;", uri.object()); activity.callMethod("startActivity","(Landroid/content/Intent;)V",intent.object()); } else { qWarning() << "ERROR: Activity not valid!"; return false; } return true; #else return false; #endif } bool QBluetoothLeUartClient::isLocationPermissionGranted() { #ifdef Q_OS_ANDROID QtAndroid::PermissionResult fineLocationAccess = QtAndroid::checkPermission("android.permission.ACCESS_FINE_LOCATION"); QtAndroid::PermissionResult coarseLocationAccess = QtAndroid::checkPermission("android.permission.ACCESS_COARSE_LOCATION"); return fineLocationAccess == QtAndroid::PermissionResult::Granted && coarseLocationAccess == QtAndroid::PermissionResult::Granted; #else return true; #endif } bool QBluetoothLeUartClient::startScanningForDevices(){ if(this->state != Idle && this->state != AdapterTurnedOff && this->state != ScanFinished && this->state != LocationPermissionDenied) return false; 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 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); } else if (error == QBluetoothDeviceDiscoveryAgent::UnknownError) { // check for permission error if(!this->isLocationPermissionGranted()) { this->setState(LocationPermissionDenied); emit this->scanningErrorOccured(LocationPermissionDeniedError); } else emit this->scanningErrorOccured(UnknownError); } 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("de.itsblue.bluetoothleuart", 1, 0, "QBluetoothLeUartDevice", "QBluetoothLeUartDevice cannot be created"); qmlRegisterUncreatableType("de.itsblue.bluetoothleuart", 1, 0, "QBluetoothLeUartDeviceModel", "QBluetoothLeUartDeviceModel cannot be created"); qmlRegisterType("de.itsblue.bluetoothleuart", 1, 0, "QBluetoothLeUart"); qRegisterMetaType("QBluetoothLeUart::BluetoothLeUartState"); qRegisterMetaType("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; }