#include "VBluetooth.h" // Qt // Project #include "BLEScanner.h" #include "FileHandler.h" #include "FileSaver.h" using namespace View; using namespace Storage; // coco begin validated: This class cannot be tested on the server, but has been validated manually 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))); connect(this, SIGNAL(didRequestReconnectToDevice(QBluetoothDeviceInfo)), &_BLEScanner, SLOT(doReselectDevice(QBluetoothDeviceInfo))); 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...")); _unpairedDevices.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)) _unpairedDevices.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(_unpairedDevices); } /*! * \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) { for (QObject *device : _unpairedDevices) { VBluetoothDeviceInfo *d = static_cast(device); if (addr == d->getAddress()) { delete _lastSelectedDevice; _lastSelectedDevice = new VBluetoothDeviceInfo(addr, d->getName()); 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 : _unpairedDevices) { VBluetoothDeviceInfo *device = static_cast(bleDevice); if (device->getAddress() == info->getAddress()) idxs.append(i); ++i; } for (int i : idxs) _unpairedDevices.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 = _unpairedDevices.begin(); iter != _unpairedDevices.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::onLoadAndConnectToLastSelectedDevice * Called when we load the ble config file and there is a previously * selected device listed * \param obj - (QJsonObject) the bledevices.conf file data */ void VBluetooth::onLoadAndConnectToLastSelectedDevice(const QJsonObject &obj) { if (obj.keys().contains(_lastSelectedDeviceAddrKey)) { QJsonObject lastSelectDeviceObj = obj.value(_lastSelectedDeviceAddrKey).toObject(); if (!lastSelectDeviceObj.isEmpty()) { if (_lastSelectedDevice == nullptr) { _lastSelectedDevice = new VBluetoothDeviceInfo(lastSelectDeviceObj.value("Address").toString(), lastSelectDeviceObj.value("Name").toString()); LOG_DEBUG(QString("Trying to connect to last selected BLE device %1").arg(_lastSelectedDevice->getAddress())); onReconnectToDevice(_lastSelectedDevice); } } } } /*! * \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; } onLoadAndConnectToLastSelectedDevice(obj); for (const QString& key : obj.keys()) { if (key != _lastSelectedDeviceAddrKey) { 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; if (_lastSelectedDevice != NULL && _lastSelectedDevice->isValid()) { QJsonObject subObj; subObj["Name"] = _lastSelectedDevice->getName(); subObj["Address"] = _lastSelectedDevice->getAddress(); obj[_lastSelectedDeviceAddrKey] = subObj; } for (QList::iterator iter = _pairedDevices.begin(); iter != _pairedDevices.end(); ++iter) obj[((VBluetoothDeviceInfo*)(*iter))->getAddress()] = ((VBluetoothDeviceInfo*)(*iter))->getName(); if (obj.isEmpty()) { LOG_DEBUG("No Bluetooth devices to save."); return; } QJsonDocument document(obj); emit didRequestConcurrentSave(path, document.toJson(), false); } /*! * \brief VBluetooth::onReconnectToDevice * Reconnects to the specified device * \param deviceInfo - the device to reconnect to */ void VBluetooth::onReconnectToDevice(const VBluetoothDeviceInfo *deviceInfo) { QBluetoothDeviceInfo device(QBluetoothAddress(deviceInfo->getAddress()), deviceInfo->getName(), 0); emit didRequestReconnectToDevice(device); } // coco end