Dokit
Internal development documentation
Loading...
Searching...
No Matches
scancommand.cpp
1// SPDX-FileCopyrightText: 2022-2024 Paul Colby <git@colby.id.au>
2// SPDX-License-Identifier: LGPL-3.0-or-later
3
4#include "scancommand.h"
5
7
8#include <QBluetoothUuid>
9#include <QJsonArray>
10#include <QJsonDocument>
11#include <QJsonObject>
12
13#include <iostream>
14
15/*!
16 * \class ScanCommand
17 *
18 * The ScanCommand class implements the `scan` CLI command, by scanning for nearby Pokit Bluetooth
19 * devices. When devices are found, they are logged to stdout in the chosen format.
20 */
21
22/*!
23 * Construct a new ScanCommand object with \a parent.
24 */
26{
27 #if (QT_VERSION >= QT_VERSION_CHECK(5, 12, 0)) // Required signal, and Fields, added in Qt 5.12.
30 #endif
31}
32
37
43
44/// \copydoc AbstractCommand::processOptions
46{
48 if (!errors.isEmpty()) {
49 return errors;
50 }
51
52 return errors;
53}
54
55/*!
56 * Begins scanning for Pokit devices.
57 */
59{
60 Q_ASSERT(discoveryAgent);
61 qCInfo(lc).noquote() << tr("Scanning for Pokit devices...");
63 return true;
64}
65
66/*!
67 * Handles discovered Pokit devices, writing \a info to stdout.
68 */
70{
71 switch (format) {
73 for (; showCsvHeader; showCsvHeader = false) {
74 std::cout << qUtf8Printable(tr("uuid,address,name,major_class,minor_class,signal_strength\n"));
75 }
76 std::cout << qUtf8Printable(QString::fromLatin1("%1,%2,%3,%4,%5,%6\n").arg(info.deviceUuid().toString(),
78 toString(info.majorDeviceClass(), info.minorDeviceClass())).arg(info.rssi()));
79 break;
81 std::cout << QJsonDocument(toJson(info)).toJson().toStdString();
82 break;
84 std::cout << qUtf8Printable(tr("%1 %2 %3 %4\n").arg(info.deviceUuid().toString(),
85 info.address().toString(), info.name()).arg(info.rssi()));
86 break;
87 }
88}
89
90/*!
91 * Handles updated Pokit devices, writing \a info to stdout. Currently \a updatedFields us unused.
92 */
93#if (QT_VERSION >= QT_VERSION_CHECK(5, 12, 0)) // Required signal, and Fields, added in Qt 5.12.
95 const QBluetoothDeviceInfo::Fields updatedFields)
96{
97 Q_UNUSED(updatedFields)
98 deviceDiscovered(info);
99}
100#endif
101
102/*!
103 * Handles the completion of device discovery. In this override we simply exit, as the scan command
104 * is nothing more than logging of discovered devices.
105 */
107{
108 qCDebug(lc).noquote() << tr("Finished scanning for Pokit devices.");
110}
111
112/*!
113 * Returns \a info as a JSON object.
114 */
116{
117 if (!info.isValid()) {
118 return QJsonObject();
119 }
120 QJsonObject json{
121 { QLatin1String("address"), info.address().toString() },
122 { QLatin1String("name"), info.name() },
123 { QLatin1String("isCached"), info.isCached() },
124 { QLatin1String("majorDeviceClass"), info.majorDeviceClass() },
125 { QLatin1String("majorDeviceClass"), toJson(info.majorDeviceClass()) },
126 { QLatin1String("minorDeviceClass"), toJson(info.majorDeviceClass(), info.minorDeviceClass()) },
127 { QLatin1String("signalStrength"), info.rssi() },
128 };
130 json.insert(QLatin1String("coreConfiguration"), toJson(info.coreConfigurations()));
131 }
132 if (!info.deviceUuid().isNull()) {
133 json.insert(QLatin1String("deviceUuid"), info.deviceUuid().toString());
134 }
135 #if (QT_VERSION >= QT_VERSION_CHECK(5, 12, 0)) // Added in Qt 5.12.
136 if (!info.manufacturerData().isEmpty()) {
137 json.insert(QLatin1String("manufacturerData"), toJson(info.manufacturerData()));
138 }
139 #endif
141 json.insert(QLatin1String("serviceClasses"), toJson(info.serviceClasses()));
142 }
143 if (!info.serviceUuids().isEmpty()) {
144 json.insert(QLatin1String("serviceUuids"), toJson(info.serviceUuids()));
145 }
146 return json;
147}
148
149/*!
150 * Returns \a configuration as a JSON array of strings.
151 */
153{
154 QJsonArray array;
155 #define DOKIT_INTERNAL_IF_SET_THEN_APPEND(flag) \
156 if (configurations.testFlag(QBluetoothDeviceInfo::flag)) \
157 array.append(QLatin1String(#flag))
158 DOKIT_INTERNAL_IF_SET_THEN_APPEND(UnknownCoreConfiguration);
159 DOKIT_INTERNAL_IF_SET_THEN_APPEND(LowEnergyCoreConfiguration);
160 DOKIT_INTERNAL_IF_SET_THEN_APPEND(BaseRateCoreConfiguration);
161 //DOKIT_INTERNAL_IF_SET_THEN_APPEND(BaseRateAndLowEnergyCoreConfiguration); // Combination flag.
162 #undef DOKIT_INTERNAL_IF_SET_THEN_APPEND
163 return array;
164}
165
166/*!
167 * Returns \a majorClass as a JSON value. This is equivalent to toString, except that if toString
168 * does not recognise \a majorClass, then \a majorClass is returned as a JSON number (not a string).
169 *
170 * \see toString(const QBluetoothDeviceInfo::MajorDeviceClass &majorClass)
171 */
173{
174 const QString string = toString(majorClass);
175 return (string.isNull() ? QJsonValue(majorClass) : QJsonValue(string));
176}
177
178/*!
179 * Returns \a minorClass as a JSON value. This is equivalent to toString, except that if toString
180 * does not recognise \a minorClass as a sub-class of \a majorClass, then \a minorClass is returned
181 * as a JSON number (not a string).
182 *
183 * \see toString(const QBluetoothDeviceInfo::MajorDeviceClass &majorClass, const quint8 minorClass)
184 */
186{
187 const QString string = toString(majorClass, minorClass);
188 return (string.isNull() ? QJsonValue(minorClass) : QJsonValue(string));
189}
190
191/*!
192 * Returns \a classes as a JSON array of strings.
193 */
195{
196 QJsonArray array;
197 #define DOKIT_INTERNAL_IF_SET_THEN_APPEND(flag) \
198 if (classes.testFlag(QBluetoothDeviceInfo::flag)) \
199 array.append(QLatin1String(#flag))
200 DOKIT_INTERNAL_IF_SET_THEN_APPEND(PositioningService);
201 DOKIT_INTERNAL_IF_SET_THEN_APPEND(NetworkingService);
202 DOKIT_INTERNAL_IF_SET_THEN_APPEND(RenderingService);
203 DOKIT_INTERNAL_IF_SET_THEN_APPEND(CapturingService);
204 DOKIT_INTERNAL_IF_SET_THEN_APPEND(ObjectTransferService);
205 DOKIT_INTERNAL_IF_SET_THEN_APPEND(AudioService);
206 DOKIT_INTERNAL_IF_SET_THEN_APPEND(TelephonyService);
207 DOKIT_INTERNAL_IF_SET_THEN_APPEND(InformationService);
208 #undef DOKIT_INTERNAL_IF_SET_THEN_APPEND
209 return array;
210}
211
212/*!
213 * Returns \a uuids as a JSON array.
214 */
216{
217 QJsonArray array;
218 for (const QBluetoothUuid &uuid: uuids) {
219 array.append(uuid.toString());
220 }
221 return array;
222}
223
224/*!
225 * Returns Bluetooth manufacturer \a data as a JSON object that maps the manufacturer IDs (unsigned
226 * integers as strings) to arrays of one or more values.
227 */
229{
230 QJsonObject object;
231 QList<quint16> keys = data.uniqueKeys();
232 std::sort(keys.begin(), keys.end());
233 for (const quint16 key: keys) {
234 // Convert the key's values to a JSON array, reversing the order, because QMultiHash
235 // guarantees that the values are orderer "from the most recently inserted to the least
236 // recently inserted", which is the oppoosit of what we want.
237 QList<QByteArray> values = data.values(key);
238 std::reverse(values.begin(), values.end());
239 QJsonArray array;
240 for (const QByteArray &value: values) {
241 array.append(QLatin1String(value.toBase64()));
242 }
243 object.insert(QString::number(key), array);
244 }
245 return object;
246}
247
248/*!
249 * Returns \a majorClass as a human-readable string, or a null QString if \a majorClass is not
250 * recognised.
251 *
252 * For example, if \a majorClass is \c QBluetoothDeviceInfo::ToyDevice, then the string `ToyDevice`
253 * is returned.
254 */
256{
257 #define DOKIT_INTERNAL_IF_EQUAL_THEN_RETURN(value) \
258 if (majorClass == QBluetoothDeviceInfo::value) \
259 return QLatin1String(#value)
260 DOKIT_INTERNAL_IF_EQUAL_THEN_RETURN(MiscellaneousDevice);
261 DOKIT_INTERNAL_IF_EQUAL_THEN_RETURN(ComputerDevice);
262 DOKIT_INTERNAL_IF_EQUAL_THEN_RETURN(PhoneDevice);
263 #if (QT_VERSION < QT_VERSION_CHECK(5, 13, 0))
264 DOKIT_INTERNAL_IF_EQUAL_THEN_RETURN(LANAccessDevice); // Deprecated since Qt 5.13.
265 #else
266 DOKIT_INTERNAL_IF_EQUAL_THEN_RETURN(NetworkDevice); // Added in Qt 5.13.
267 #endif
268 DOKIT_INTERNAL_IF_EQUAL_THEN_RETURN(AudioVideoDevice);
269 DOKIT_INTERNAL_IF_EQUAL_THEN_RETURN(PeripheralDevice);
270 DOKIT_INTERNAL_IF_EQUAL_THEN_RETURN(ImagingDevice);
271 DOKIT_INTERNAL_IF_EQUAL_THEN_RETURN(WearableDevice);
272 DOKIT_INTERNAL_IF_EQUAL_THEN_RETURN(ToyDevice);
273 DOKIT_INTERNAL_IF_EQUAL_THEN_RETURN(HealthDevice);
274 DOKIT_INTERNAL_IF_EQUAL_THEN_RETURN(UncategorizedDevice);
275 #undef DOKIT_INTERNAL_IF_EQUAL_THEN_RETURN
276 qCDebug(lc).noquote() << tr("Unknown major class %1.").arg(majorClass);
277 return QString(); // Null QString indicates unknown minor class.
278}
279
280/*!
281 * Returns \a minorClass as a human-readable string, or a null QString if \a minorClass is not
282 * recognised as a sub-class of \a majorClass.
283 *
284 * For example, if \a majorClass is \c QBluetoothDeviceInfo::ToyDevice, and \a minorClass is
285 * \c QBluetoothDeviceInfo::ToyRobot, then the string `ToyRobot` is returned.
286 */
288{
289 #define DOKIT_INTERNAL_IF_EQUAL_THEN_RETURN(value) \
290 if (minorClass == QBluetoothDeviceInfo::value) \
291 return QLatin1String(#value)
292 switch (majorClass) {
294 DOKIT_INTERNAL_IF_EQUAL_THEN_RETURN(UncategorizedMiscellaneous);
295 break;
297 DOKIT_INTERNAL_IF_EQUAL_THEN_RETURN(UncategorizedComputer);
298 DOKIT_INTERNAL_IF_EQUAL_THEN_RETURN(DesktopComputer);
299 DOKIT_INTERNAL_IF_EQUAL_THEN_RETURN(ServerComputer);
300 DOKIT_INTERNAL_IF_EQUAL_THEN_RETURN(LaptopComputer);
301 DOKIT_INTERNAL_IF_EQUAL_THEN_RETURN(HandheldClamShellComputer);
302 DOKIT_INTERNAL_IF_EQUAL_THEN_RETURN(HandheldComputer);
303 DOKIT_INTERNAL_IF_EQUAL_THEN_RETURN(WearableComputer);
304 break;
306 DOKIT_INTERNAL_IF_EQUAL_THEN_RETURN(UncategorizedPhone);
307 DOKIT_INTERNAL_IF_EQUAL_THEN_RETURN(CellularPhone);
308 DOKIT_INTERNAL_IF_EQUAL_THEN_RETURN(CordlessPhone);
309 DOKIT_INTERNAL_IF_EQUAL_THEN_RETURN(SmartPhone);
310 DOKIT_INTERNAL_IF_EQUAL_THEN_RETURN(WiredModemOrVoiceGatewayPhone);
311 DOKIT_INTERNAL_IF_EQUAL_THEN_RETURN(CommonIsdnAccessPhone);
312 break;
313 #if (QT_VERSION < QT_VERSION_CHECK(5, 13, 0))
314 case QBluetoothDeviceInfo::LANAccessDevice: // Deprecated since Qt 5.13.
315 #else
316 case QBluetoothDeviceInfo::NetworkDevice: // Added in Qt 5.13.
317 #endif
318 DOKIT_INTERNAL_IF_EQUAL_THEN_RETURN(NetworkFullService);
319 DOKIT_INTERNAL_IF_EQUAL_THEN_RETURN(NetworkLoadFactorOne);
320 DOKIT_INTERNAL_IF_EQUAL_THEN_RETURN(NetworkLoadFactorTwo);
321 DOKIT_INTERNAL_IF_EQUAL_THEN_RETURN(NetworkLoadFactorThree);
322 DOKIT_INTERNAL_IF_EQUAL_THEN_RETURN(NetworkLoadFactorFour);
323 DOKIT_INTERNAL_IF_EQUAL_THEN_RETURN(NetworkLoadFactorFive);
324 DOKIT_INTERNAL_IF_EQUAL_THEN_RETURN(NetworkLoadFactorSix);
325 DOKIT_INTERNAL_IF_EQUAL_THEN_RETURN(NetworkNoService);
326 break;
328 DOKIT_INTERNAL_IF_EQUAL_THEN_RETURN(UncategorizedAudioVideoDevice);
329 DOKIT_INTERNAL_IF_EQUAL_THEN_RETURN(WearableHeadsetDevice);
330 DOKIT_INTERNAL_IF_EQUAL_THEN_RETURN(HandsFreeDevice);
331 DOKIT_INTERNAL_IF_EQUAL_THEN_RETURN(Microphone);
332 DOKIT_INTERNAL_IF_EQUAL_THEN_RETURN(Loudspeaker);
333 DOKIT_INTERNAL_IF_EQUAL_THEN_RETURN(Headphones);
334 DOKIT_INTERNAL_IF_EQUAL_THEN_RETURN(PortableAudioDevice);
335 DOKIT_INTERNAL_IF_EQUAL_THEN_RETURN(CarAudio);
336 DOKIT_INTERNAL_IF_EQUAL_THEN_RETURN(SetTopBox);
337 DOKIT_INTERNAL_IF_EQUAL_THEN_RETURN(HiFiAudioDevice);
338 DOKIT_INTERNAL_IF_EQUAL_THEN_RETURN(Vcr);
339 DOKIT_INTERNAL_IF_EQUAL_THEN_RETURN(VideoCamera);
340 DOKIT_INTERNAL_IF_EQUAL_THEN_RETURN(Camcorder);
341 DOKIT_INTERNAL_IF_EQUAL_THEN_RETURN(VideoMonitor);
342 DOKIT_INTERNAL_IF_EQUAL_THEN_RETURN(VideoDisplayAndLoudspeaker);
343 DOKIT_INTERNAL_IF_EQUAL_THEN_RETURN(VideoConferencing);
344 DOKIT_INTERNAL_IF_EQUAL_THEN_RETURN(GamingDevice);
345 break;
347 DOKIT_INTERNAL_IF_EQUAL_THEN_RETURN(UncategorizedPeripheral);
348 DOKIT_INTERNAL_IF_EQUAL_THEN_RETURN(KeyboardPeripheral);
349 DOKIT_INTERNAL_IF_EQUAL_THEN_RETURN(PointingDevicePeripheral);
350 DOKIT_INTERNAL_IF_EQUAL_THEN_RETURN(KeyboardWithPointingDevicePeripheral);
351 DOKIT_INTERNAL_IF_EQUAL_THEN_RETURN(JoystickPeripheral);
352 DOKIT_INTERNAL_IF_EQUAL_THEN_RETURN(GamepadPeripheral);
353 DOKIT_INTERNAL_IF_EQUAL_THEN_RETURN(RemoteControlPeripheral);
354 DOKIT_INTERNAL_IF_EQUAL_THEN_RETURN(SensingDevicePeripheral);
355 DOKIT_INTERNAL_IF_EQUAL_THEN_RETURN(DigitizerTabletPeripheral);
356 DOKIT_INTERNAL_IF_EQUAL_THEN_RETURN(CardReaderPeripheral);
357 break;
359 DOKIT_INTERNAL_IF_EQUAL_THEN_RETURN(UncategorizedImagingDevice);
360 DOKIT_INTERNAL_IF_EQUAL_THEN_RETURN(ImageDisplay);
361 DOKIT_INTERNAL_IF_EQUAL_THEN_RETURN(ImageCamera);
362 DOKIT_INTERNAL_IF_EQUAL_THEN_RETURN(ImageScanner);
363 DOKIT_INTERNAL_IF_EQUAL_THEN_RETURN(ImagePrinter);
364 break;
366 DOKIT_INTERNAL_IF_EQUAL_THEN_RETURN(UncategorizedWearableDevice);
367 DOKIT_INTERNAL_IF_EQUAL_THEN_RETURN(WearableWristWatch);
368 DOKIT_INTERNAL_IF_EQUAL_THEN_RETURN(WearablePager);
369 DOKIT_INTERNAL_IF_EQUAL_THEN_RETURN(WearableJacket);
370 DOKIT_INTERNAL_IF_EQUAL_THEN_RETURN(WearableHelmet);
371 DOKIT_INTERNAL_IF_EQUAL_THEN_RETURN(WearableGlasses);
372 break;
374 DOKIT_INTERNAL_IF_EQUAL_THEN_RETURN(UncategorizedToy);
375 DOKIT_INTERNAL_IF_EQUAL_THEN_RETURN(ToyRobot);
376 DOKIT_INTERNAL_IF_EQUAL_THEN_RETURN(ToyVehicle);
377 DOKIT_INTERNAL_IF_EQUAL_THEN_RETURN(ToyDoll);
378 DOKIT_INTERNAL_IF_EQUAL_THEN_RETURN(ToyController);
379 DOKIT_INTERNAL_IF_EQUAL_THEN_RETURN(ToyGame);
380 break;
382 DOKIT_INTERNAL_IF_EQUAL_THEN_RETURN(UncategorizedHealthDevice);
383 DOKIT_INTERNAL_IF_EQUAL_THEN_RETURN(HealthBloodPressureMonitor);
384 DOKIT_INTERNAL_IF_EQUAL_THEN_RETURN(HealthThermometer);
385 DOKIT_INTERNAL_IF_EQUAL_THEN_RETURN(HealthWeightScale);
386 DOKIT_INTERNAL_IF_EQUAL_THEN_RETURN(HealthGlucoseMeter);
387 DOKIT_INTERNAL_IF_EQUAL_THEN_RETURN(HealthPulseOximeter);
388 DOKIT_INTERNAL_IF_EQUAL_THEN_RETURN(HealthDataDisplay);
389 DOKIT_INTERNAL_IF_EQUAL_THEN_RETURN(HealthStepCounter);
390 break;
392 // There are no minor classes defined (in Qt) for uncategorized devices.
393 break;
394 }
395 #undef DOKIT_INTERNAL_IF_EQUAL_THEN_RETURN
396 qCDebug(lc).noquote() << tr("Unknown minor class %1 for major class %2.")
397 .arg(minorClass).arg(majorClass);
398 return QString(); // Null QString indicates unknown minor class.
399}
The AbstractCommand class provides a consistent base for the classes that implement CLI commands.
virtual QStringList supportedOptions(const QCommandLineParser &parser) const
Returns a list of CLI option names supported by this command.
PokitDiscoveryAgent * discoveryAgent
Agent for Pokit device descovery.
OutputFormat format
Selected output format.
@ Text
Plain unstructured text.
@ Csv
RFC 4180 compliant CSV text.
@ Json
RFC 8259 compliant JSON text.
virtual QStringList processOptions(const QCommandLineParser &parser)
Processes the relevant options from the command line parser.
static QString escapeCsvField(const QString &field)
Returns an RFC 4180 compliant version of field.
virtual QStringList requiredOptions(const QCommandLineParser &parser) const
Returns a list of CLI option names required by this command.
void start(QBluetoothDeviceDiscoveryAgent::DiscoveryMethods methods)
Starts Pokit device discovery.
void pokitDeviceUpdated(const QBluetoothDeviceInfo &info, QBluetoothDeviceInfo::Fields updatedFields)
This signal is emitted when the Pokit device described by info is updated.
static QJsonObject toJson(const QBluetoothDeviceInfo &info)
Returns info as a JSON object.
QStringList requiredOptions(const QCommandLineParser &parser) const override
Returns a list of CLI option names required by this command.
void deviceUpdated(const QBluetoothDeviceInfo &info, const QBluetoothDeviceInfo::Fields updatedFields)
Handles updated Pokit devices, writing info to stdout.
ScanCommand(QObject *const parent=nullptr)
Construct a new ScanCommand object with parent.
static QString toString(const QBluetoothDeviceInfo::MajorDeviceClass &majorClass)
Returns majorClass as a human-readable string, or a null QString if majorClass is not recognised.
void deviceDiscoveryFinished() override
Handles the completion of device discovery.
bool start() override
Begins scanning for Pokit devices.
QStringList processOptions(const QCommandLineParser &parser) override
Processes the relevant options from the command line parser.
bool showCsvHeader
Whether or not to show a header as the first line of CSV output.
Definition scancommand.h:29
void deviceDiscovered(const QBluetoothDeviceInfo &info) override
Handles discovered Pokit devices, writing info to stdout.
QStringList supportedOptions(const QCommandLineParser &parser) const override
Returns a list of CLI option names supported by this command.
Declares the PokitDiscoveryAgent class.
QString toString() const const
QBluetoothAddress address() const const
QBluetoothDeviceInfo::CoreConfigurations coreConfigurations() const const
QBluetoothUuid deviceUuid() const const
bool isCached() const const
bool isValid() const const
QBluetoothDeviceInfo::MajorDeviceClass majorDeviceClass() const const
QByteArray manufacturerData(quint16 manufacturerId) const const
quint8 minorDeviceClass() const const
QString name() const const
qint16 rssi() const const
QBluetoothDeviceInfo::ServiceClasses serviceClasses() const const
QList< QBluetoothUuid > serviceUuids(QBluetoothDeviceInfo::DataCompleteness *completeness) const const
bool isEmpty() const const
std::string toStdString() const const
void append(const QJsonValue &value)
QByteArray toJson() const const
QList::iterator begin()
QList::iterator end()
bool isEmpty() const const
QList< Key > uniqueKeys() const const
QList< T > values(const Key &key) const const
QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
QString tr(const char *sourceText, const char *disambiguation, int n)
QString arg(qlonglong a, int fieldWidth, int base, QChar fillChar) const const
QString fromLatin1(const char *str, int size)
QString number(int n, int base)
bool isNull() const const
QString toString() const const