#include "VBluetooth.h" // Qt // Project #include "BLEScanner.h" #include "FileHandler.h" #include "FileSaver.h" using namespace View; using namespace Storage; VBluetooth::VBluetooth(QObject *parent) : QObject(parent) { // incoming connect(&_BLEScanner, SIGNAL(didReceiveScanForDevicesError(QBluetoothDeviceDiscoveryAgent::Error)), this, SLOT(onScanForDevicesError(QBluetoothDeviceDiscoveryAgent::Error))); connect(&_BLEScanner, SIGNAL(didFinishScan()), this, SLOT(onScanForDevicesFinished())); connect(&_BLEScanner, SIGNAL(didDiscoverDevice(QBluetoothDeviceInfo)), this, SLOT(onDeviceDiscovered(const QBluetoothDeviceInfo))); connect(&_BLEScanner, SIGNAL(didStartConnectingToDevice()), this, SLOT(onConnectingToDevice())); connect(&_BLEScanner, SIGNAL(didReceiveControllerError(QLowEnergyController::Error)), this, SLOT(onControllerError(QLowEnergyController::Error))); connect(&_BLEScanner, SIGNAL(didConnectToDevice(QBluetoothDeviceInfo)), this, SLOT(onConnectedToDevice(QBluetoothDeviceInfo))); connect(&_BLEScanner, SIGNAL(didDisconnectFromDevice(QBluetoothDeviceInfo)), this, SLOT(onDisconnectedFromDevice(QBluetoothDeviceInfo))); // outgoing connect(this, SIGNAL(didSelectDevice(const QString)), &_BLEScanner, SLOT(doSelectDevice(const QString))); connect(this, SIGNAL(didRequestScanForDevices()), &_BLEScanner, SLOT(doScanForDevices())); connect(this, SIGNAL(didRequestConcurrentSave(QString,QString,bool)), &_FileSaver, SLOT(onConcurrentSave(QString,QString,bool))); onLoadMyDevices(); } /*! * \brief VBluetooth::onScanForDevices * Slot for when the user starts scanning for devices. * Updates the status and emits a signal to the BLEScanner to * begin a new scan. */ void VBluetooth::doScanForDevices() { onUpdateStatus(tr("Scanning...")); bleDevices.clear(); emit didDevicesChanged(); emit didRequestScanForDevices(); } /*! * \brief VBluetooth::onScanForDevicesError * Slot called when the BLEScanner enounters an error * while scanning for devices * \param error */ void VBluetooth::onScanForDevicesError(QBluetoothDeviceDiscoveryAgent::Error error) { switch (error) { case QBluetoothDeviceDiscoveryAgent::PoweredOffError: onUpdateStatus(tr("Error: Bluetooth module is powered off.")); break; default: onUpdateStatus(tr("Error: Scan for devices error.")); break; } } /*! * \brief VBluetooth::onDeviceDiscovered * Slot for when a device is discovered by the BLEScanner. * \param device - the BLE Device info of the discovered device */ void VBluetooth::onDeviceDiscovered(const QBluetoothDeviceInfo &device) { VBluetoothDeviceInfo *info = new VBluetoothDeviceInfo(device); if (!isDeviceAlreadyPaired(device)) bleDevices.insert(0, info); qDebug() << "VBluetooth: Discovered " << device.name(); emit didDevicesChanged(); } /*! * \brief VBluetooth::onScanForDevicesFinished * Slot called when the scan is finished. Updates the scanning status. */ void VBluetooth::onScanForDevicesFinished() { emit didScanFinished(); onUpdateStatus(tr("Scan Finished.")); } /*! * \brief VBluetooth::getDevices * Gets the BLE modelData for QML * \return QVariant - the modelData */ QVariant VBluetooth::doGetDevices() { return QVariant::fromValue(bleDevices); } /*! * \brief VBluetooth::getDevices * Gets the paired devices BLE modelData for QML * \return QVariant - the modelData */ QVariant VBluetooth::doGetPairedDevices() { return QVariant::fromValue(pairedDevices); } /*! * \brief VBluetooth::onSelectedDevice * Emits a signal that the device was selected * \param addr - the selected BLE mac address */ void VBluetooth::doSelectDevice(const QString &addr) { emit didSelectDevice(addr); } /*! * \brief onUpdateStatus * Updates the status and emits a signal to notify the QML * \param message */ void VBluetooth::onUpdateStatus(const QString &message) { status = message; emit didStatusChanged(); } /*! * \brief VBluetooth::onConnectingToDevice * Called when attempting to connect to a device */ void VBluetooth::onConnectingToDevice() { onUpdateStatus(tr("Connecting...")); } /*! * \brief VBluetooth::controllerErrorToString * Converts a QLowEnergyController::Error enum to QString * \param error - the QLowEnergyController::Error enum * \return QString - the enum converted to a QString */ QString VBluetooth::controllerErrorToString(QLowEnergyController::Error error) { switch (error) { case QLowEnergyController::NoError: return tr("No Error."); break; case QLowEnergyController::UnknownError: return tr("Unknown Error. Is device turned off?"); break; case QLowEnergyController::UnknownRemoteDeviceError: return tr("Unknown Remote Device Error."); break; case QLowEnergyController::NetworkError: return tr("Bluetooth Network Error."); break; case QLowEnergyController::InvalidBluetoothAdapterError: return tr("Bluetooth Adapter Error."); break; case QLowEnergyController::ConnectionError: case QLowEnergyController::AdvertisingError: case QLowEnergyController::RemoteHostClosedError: return tr("Connection Error."); break; default: const QMetaObject *mo = qt_getEnumMetaObject(error); int idx = mo->indexOfEnumerator(qt_getEnumName(error)); const char *text = mo->enumerator(idx).valueToKey(error); if (!QString(text).isEmpty()) { return tr(text); } } return tr("Unexpected Error."); } /*! * \brief VBluetooth::onControllerError * Called when BLE interface reports a controller error. Updates * the bluetooth status message according to the error reported. * \param error - the low energy controller error reported */ void VBluetooth::onControllerError(QLowEnergyController::Error error) { onUpdateStatus(controllerErrorToString(error)); } /*! * \brief VBluetooth::isDeviceAlreadyPaired * Determines if a device has already been paired * \param deviceInfo - the device info to check if paired * \return bool - true if already paired, false otherwise */ bool VBluetooth::isDeviceAlreadyPaired(const QBluetoothDeviceInfo &deviceInfo) { for (QObject *device : pairedDevices) { VBluetoothDeviceInfo *pairedDevice = static_cast(device); if (pairedDevice->getAddress() == deviceInfo.address().toString()) return true; } return false; } /*! * \brief VBluetooth::removeFromDevices * Removes a device from the non-paired bluetooth devices list * Adds that devices to the non-paired bluetooth devices blacklist * \param info - (VBluetoothDeviceInfo*) the bluetooth device info object to remove */ void VBluetooth::removeFromDevices(const VBluetoothDeviceInfo *info) { QList idxs; int i = 0; for (QObject *bleDevice : bleDevices) { VBluetoothDeviceInfo *device = static_cast(bleDevice); if (device->getAddress() == info->getAddress()) idxs.append(i); ++i; } for (int i : idxs) bleDevices.removeAt(i); // update the QML emit didDevicesChanged(); } /*! * \brief VBluetooth::onConnectedToDevice * Slot called when BLEScanner connects to a device. Moves a device * from the devices list to the my devices list * \param deviceInfo - (QBluetoothDeviceInfo) the device connected to */ void VBluetooth::onConnectedToDevice(const QBluetoothDeviceInfo &deviceInfo) { VBluetoothDeviceInfo *info = new VBluetoothDeviceInfo(deviceInfo); info->setConnected(true); bool alreadyPaired = isDeviceAlreadyPaired(deviceInfo); if (alreadyPaired) { setDeviceConnected(deviceInfo, true); } else { pairedDevices.append(info); emit didPairedDevicesChanged(); removeFromDevices(info); } onUpdateStatus(tr("Paired, Connected.")); } /*! * \brief VBluetooth::setDeviceConnected * Ensures the device displays as connected * \param deviceInfo - (QBluetoothDeviceInfo) contains the device information * \param connected - (bool) true to set connected, false to set not connected */ void VBluetooth::setDeviceConnected(const QBluetoothDeviceInfo &deviceInfo, const bool &connected) { if (isDeviceAlreadyPaired(deviceInfo)) { for (QList::iterator iter = pairedDevices.begin(); iter != pairedDevices.end(); ++iter) { if (deviceInfo.address().toString() == ((VBluetoothDeviceInfo*)(*iter))->getAddress()) ((VBluetoothDeviceInfo*)(*iter))->setConnected(connected); } emit didPairedDevicesChanged(); } else { for (QList::iterator iter = bleDevices.begin(); iter != bleDevices.end(); ++iter) { if (deviceInfo.address().toString() == ((VBluetoothDeviceInfo*)(*iter))->getAddress()) ((VBluetoothDeviceInfo*)(*iter))->setConnected(connected); } emit didDevicesChanged(); } } /*! * \brief VBluetooth::onDisconnectedFromDevice * Slot called when the BLEScanner detects that a device disconnected. Updates * the status of the device to Not Connected if is has already been paired * \param deviceInfo - (QBluetoothDeviceInfo) contains the device information */ void VBluetooth::onDisconnectedFromDevice(const QBluetoothDeviceInfo &deviceInfo) { setDeviceConnected(deviceInfo, false); emit didPairedDevicesChanged(); onUpdateStatus(tr("Paired, Not Connected.")); } /*! * \brief VBluetooth::loadMyDevices * Loads previously selected devices from disk */ void VBluetooth::onLoadMyDevices(const QString &path) { pairedDevices.clear(); QJsonObject obj; if (!FileHandler::readJSON(path, obj)) { LOG_EVENT(tr("Could not load my bluetooth devices from %1").arg(path)); return; } for (const QString& key : obj.keys()) { VBluetoothDeviceInfo *info = new VBluetoothDeviceInfo(key, obj[key].toString()); pairedDevices.append(info); } } /*! * \brief VBluetooth::saveMyDevices * Saves selected devices to disk */ void VBluetooth::doSaveMyDevices(const QString &path) { QJsonObject obj; for (QList::iterator iter = pairedDevices.begin(); iter != pairedDevices.end(); ++iter) obj[((VBluetoothDeviceInfo*)(*iter))->getAddress()] = ((VBluetoothDeviceInfo*)(*iter))->getName(); QJsonDocument document(obj); emit didRequestConcurrentSave(path, document.toJson(), false); }