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