Line data Source code
1 : // SPDX-FileCopyrightText: 2022-2023 Paul Colby <git@colby.id.au> 2 : // SPDX-License-Identifier: LGPL-3.0-or-later 3 : 4 : #include "devicecommand.h" 5 : 6 : #include <qtpokit/abstractpokitservice.h> 7 : #include <qtpokit/pokitdevice.h> 8 : #include <qtpokit/pokitdiscoveryagent.h> 9 : 10 : /*! 11 : * \class DeviceCommand 12 : * 13 : * The AbstractCommand class extends AbstractCommand to add a PokitDevice instance. 14 : */ 15 : 16 : /*! 17 : * Construct a new DeviceCommand object with \a parent. 18 : */ 19 6486 : DeviceCommand::DeviceCommand(QObject * const parent) : AbstractCommand(parent) 20 : { 21 : 22 6486 : } 23 : 24 : /*! 25 : * Begins scanning for the Pokit device. 26 : */ 27 36 : bool DeviceCommand::start() 28 : { 29 72 : qCInfo(lc).noquote() << ((deviceToScanFor.isNull()) 30 64 : ? tr("Looking for first available Pokit device...") 31 76 : : tr("Looking for device \"%1\"...").arg(deviceToScanFor)); 32 36 : discoveryAgent->start(); 33 36 : return true; 34 : } 35 : 36 : /*! 37 : * Disconnects the underlying Pokit device, and sets \a exitCode to be return to the OS once the 38 : * disconnection has taken place. 39 : */ 40 474 : void DeviceCommand::disconnect(int exitCode) 41 : { 42 474 : qCDebug(lc).noquote() << tr("Disconnecting Pokit device..."); 43 : Q_ASSERT(device); 44 : Q_ASSERT(device->controller()); 45 474 : exitCodeOnDisconnect = exitCode; 46 474 : device->controller()->disconnectFromDevice(); 47 474 : } 48 : 49 : /*! 50 : * \fn virtual AbstractPokitService * DeviceCommand::getService() = 0 51 : * 52 : * Returns a Pokit service object for the derived command class. This will be called by 53 : * deviceDiscovered() when the requested Pokit device has been found, after which 54 : * deviceDiscovered() will connect the returned service's common signals, and kick off the 55 : * device's connection process. 56 : */ 57 : 58 : /*! 59 : * Handles controller error events. This base implementation simply logs \a error and then exits 60 : * with `EXIT_FAILURE`. Derived classes may override this slot to implement their own error 61 : * handing if desired. 62 : */ 63 36 : void DeviceCommand::controllerError(QLowEnergyController::Error error) 64 : { 65 108 : qCWarning(lc).noquote() << tr("Bluetooth controller error:") << error; 66 36 : QCoreApplication::exit(EXIT_FAILURE); 67 36 : } 68 : 69 : /*! 70 : * Handles devics disconnection events. This base implementation simply logs and exits the 71 : * application (via QCoreApplication::exit) with the current exitCodeOnDisconnect value, which is 72 : * initialise to `EXIT_FAILURE` in the constructor, but should be set to `EXIT_SUCESS` if/when 73 : * the derived command class has completed its actions and requested the disconnection (as opposed 74 : * to a spontaneous disconnection on error). 75 : */ 76 18 : void DeviceCommand::deviceDisconnected() 77 : { 78 18 : qCDebug(lc).noquote() << tr("Pokit device disconnected. Exiting with code %1.") 79 0 : .arg(exitCodeOnDisconnect); 80 18 : QCoreApplication::exit(exitCodeOnDisconnect); 81 18 : } 82 : 83 : /*! 84 : * Handles service error events. This base implementation simply logs \a error and then exits 85 : * with `EXIT_FAILURE`. Derived classes may override this slot to implement their own error 86 : * handing if desired. 87 : * 88 : * \note As this base class does not construct services (derived classed do), its up to the derived 89 : * classed to connect this slot to the relevant service's error signal if desired. 90 : */ 91 36 : void DeviceCommand::serviceError(const QLowEnergyService::ServiceError error) 92 : { 93 108 : qCWarning(lc).noquote() << tr("Bluetooth service error:") << error; 94 36 : QCoreApplication::exit(EXIT_FAILURE); 95 36 : } 96 : 97 : /*! 98 : * Handles service detail discovery events. This base implementation simply logs the event, and 99 : * nothing more. Derived classes may (usually do) override this slot to provide their own processing 100 : * when a services details have been discovered. 101 : */ 102 348 : void DeviceCommand::serviceDetailsDiscovered() 103 : { 104 348 : qCDebug(lc).noquote() << tr("Service details discovered."); 105 348 : } 106 : 107 : /*! 108 : * Checks if \a info is the device (if any) we're looking for, and if so, create a contoller and 109 : * service, and begins connecting to the device. 110 : */ 111 18 : void DeviceCommand::deviceDiscovered(const QBluetoothDeviceInfo &info) 112 : { 113 18 : if (device) { 114 0 : qCDebug(lc).noquote() << tr("Ignoring additional Pokit device \"%1\" (%2) at (%3).") 115 0 : .arg(info.name(), info.deviceUuid().toString(), info.address().toString()); 116 0 : return; 117 : } 118 : 119 54 : if ((deviceToScanFor.isEmpty()) || (deviceToScanFor == info.name()) || 120 72 : ((!info.address().isNull()) && (info.address() == QBluetoothAddress(deviceToScanFor))) || 121 36 : ((!info.deviceUuid().isNull()) && (info.deviceUuid() == QBluetoothUuid(deviceToScanFor)))) 122 : { 123 0 : qCDebug(lc).noquote() << tr("Found Pokit device \"%1\" (%2) at (%3).") 124 0 : .arg(info.name(), info.deviceUuid().toString(), info.address().toString()); 125 0 : discoveryAgent->stop(); 126 : 127 0 : device = new PokitDevice(info, this); 128 0 : connect(device->controller(), &QLowEnergyController::disconnected, 129 : this, &DeviceCommand::deviceDisconnected); 130 0 : connect(device->controller(), 131 : #if (QT_VERSION < QT_VERSION_CHECK(6, 2, 0)) 132 : QOverload<QLowEnergyController::Error>::of(&QLowEnergyController::error), 133 : #else 134 : &QLowEnergyController::errorOccurred, 135 : #endif 136 : this, &DeviceCommand::controllerError, Qt::QueuedConnection); 137 : 138 0 : const AbstractPokitService * const service = getService(); 139 : 140 : Q_ASSERT(service); 141 0 : connect(service, &AbstractPokitService::serviceDetailsDiscovered, 142 : this, &DeviceCommand::serviceDetailsDiscovered); 143 0 : connect(service, &AbstractPokitService::serviceErrorOccurred, 144 : this, &DeviceCommand::serviceError); 145 : 146 0 : qCDebug(lc).noquote() << tr("Connecting to Pokit device \"%1\" (%2) at (%3).") 147 0 : .arg(info.name(), info.deviceUuid().toString(), info.address().toString()); 148 0 : device->controller()->connectToDevice(); 149 0 : return; 150 : } 151 : 152 18 : qCDebug(lc).noquote() << tr("Ignoring non-matching Pokit device \"%1\" (%2) at (%3).") 153 0 : .arg(info.name(), info.deviceUuid().toString(), info.address().toString()); 154 3 : return; 155 : } 156 : 157 : /*! 158 : * Checks that the requested device was discovered, and if not, reports and error and exits. 159 : */ 160 36 : void DeviceCommand::deviceDiscoveryFinished() 161 : { 162 36 : if (!device) { 163 72 : qCWarning(lc).noquote() << ((deviceToScanFor.isNull()) 164 64 : ? tr("Failed to find any Pokit device.") 165 76 : : tr("Failed to find device \"%1\".").arg(deviceToScanFor)); 166 36 : QCoreApplication::exit(EXIT_FAILURE); 167 : } 168 36 : }