3#if __has_include(<QBluetoothDeviceDiscoveryAgent>)
4#define OSSIA_HAS_BLUETOOTH 1
8#include <ossia-qt/protocols/utils.hpp>
10#include <QBluetoothDeviceDiscoveryAgent>
11#include <QBluetoothDeviceInfo>
12#include <QBluetoothSocket>
13#include <QBluetoothUuid>
19#include <QLowEnergyCharacteristic>
20#include <QLowEnergyController>
21#include <QLowEnergyDescriptor>
22#include <QLowEnergyService>
24#include <nano_observer.hpp>
31static QBluetoothUuid bleUuidFromString(
const QString& str)
36 auto val = str.toUInt(&ok, 16);
38 return QBluetoothUuid{
static_cast<quint16
>(val)};
40 return QBluetoothUuid{QUuid::fromString(str)};
43class qml_bluetooth_scanner
45 ,
public Nano::Observer
47 W_OBJECT(qml_bluetooth_scanner)
49 qml_bluetooth_scanner()
51 m_agent =
new QBluetoothDeviceDiscoveryAgent(
this);
54 m_agent, &QBluetoothDeviceDiscoveryAgent::deviceDiscovered,
this,
55 [
this](
const QBluetoothDeviceInfo& info) {
56 if(!onDeviceDiscovered.isCallable())
58 auto* engine = qjsEngine(
this);
62 auto dev = engine->newObject();
63 dev.setProperty(
"Name", info.name());
64 dev.setProperty(
"Address", info.address().toString());
66 "DeviceUuid", info.deviceUuid().toString(QUuid::WithoutBraces));
67 dev.setProperty(
"RSSI", info.rssi());
70 info.coreConfigurations().testFlag(
71 QBluetoothDeviceInfo::LowEnergyCoreConfiguration));
74 info.coreConfigurations().testFlag(
75 QBluetoothDeviceInfo::BaseRateCoreConfiguration));
78 static_cast<int>(info.majorDeviceClass()));
79 dev.setProperty(
"MinorDeviceClass", info.minorDeviceClass());
81 auto uuids = info.serviceUuids();
82 auto serviceArr = engine->newArray(uuids.size());
83 for(qsizetype i = 0; i < uuids.size(); ++i)
84 serviceArr.setProperty(i, uuids[i].toString(QUuid::WithoutBraces));
85 dev.setProperty(
"ServiceUuids", serviceArr);
87 auto mfData = info.manufacturerData();
90 auto mfObj = engine->newObject();
91 for(
auto it = mfData.begin(); it != mfData.end(); ++it)
93 QString::number(it.key()), engine->toScriptValue(it.value()));
94 dev.setProperty(
"ManufacturerData", mfObj);
97 onDeviceDiscovered.call({dev});
101 m_agent, &QBluetoothDeviceDiscoveryAgent::finished,
this, [
this]() {
102 if(onFinished.isCallable())
107 m_agent, &QBluetoothDeviceDiscoveryAgent::errorOccurred,
this,
108 [
this](QBluetoothDeviceDiscoveryAgent::Error) {
109 if(onError.isCallable())
110 onError.call({m_agent->errorString()});
114 ~qml_bluetooth_scanner() { stop(); }
119 QBluetoothDeviceDiscoveryAgent::ClassicMethod
120 | QBluetoothDeviceDiscoveryAgent::LowEnergyMethod);
126 m_agent->start(QBluetoothDeviceDiscoveryAgent::LowEnergyMethod);
132 m_agent->start(QBluetoothDeviceDiscoveryAgent::ClassicMethod);
136 void stop() { m_agent->stop(); }
139 void setLowEnergyDiscoveryTimeout(
int msTimeout)
141 m_agent->setLowEnergyDiscoveryTimeout(msTimeout);
143 W_SLOT(setLowEnergyDiscoveryTimeout)
145 QJSValue onDeviceDiscovered;
150 QBluetoothDeviceDiscoveryAgent* m_agent{};
153class qml_bluetooth_socket
155 ,
public Nano::Observer
157 W_OBJECT(qml_bluetooth_socket)
161 std::atomic_bool alive{
true};
164 qml_bluetooth_socket(
165 const QBluetoothAddress& address,
const QBluetoothUuid& serviceUuid)
166 : m_state{std::make_shared<state>()}
168 , m_serviceUuid{serviceUuid}
170 m_socket =
new QBluetoothSocket(QBluetoothServiceInfo::RfcommProtocol,
this);
174 m_socket, &QBluetoothSocket::connected,
this, [
this, st]() {
178 if(!onOpen.isCallable())
180 auto* engine = qjsEngine(
this);
183 onOpen.call({engine->newQObject(
this)});
187 m_socket, &QBluetoothSocket::disconnected,
this, [
this, st]() {
191 if(onClose.isCallable())
196 m_socket, &QBluetoothSocket::readyRead,
this, [
this, st]() {
199 if(!onMessage.isCallable())
201 auto data = m_socket->readAll();
202 auto* engine = qjsEngine(
this);
204 onMessage.call({engine->toScriptValue(data)});
208 m_socket, &QBluetoothSocket::errorOccurred,
this,
209 [
this, st](QBluetoothSocket::SocketError) {
212 if(onError.isCallable())
213 onError.call({m_socket->errorString()});
217 ~qml_bluetooth_socket()
219 m_state->alive =
false;
223 void open() { m_socket->connectToService(m_address, m_serviceUuid); }
225 void write(QByteArray data)
228 m_socket->write(data);
237 m_socket->disconnectFromService();
248 std::shared_ptr<state> m_state;
249 QBluetoothSocket* m_socket{};
250 QBluetoothAddress m_address;
251 QBluetoothUuid m_serviceUuid;
252 std::atomic_bool m_is_open{
false};
257 ,
public Nano::Observer
259 W_OBJECT(qml_ble_service)
261 explicit qml_ble_service(QLowEnergyService* service, QObject* parent)
266 m_service, &QLowEnergyService::stateChanged,
this,
267 [
this](QLowEnergyService::ServiceState state) {
268 if(state != QLowEnergyService::RemoteServiceDiscovered)
270 if(!onDetailsDiscovered.isCallable())
272 auto* engine = qjsEngine(
this);
276 auto chars = m_service->characteristics();
277 auto arr = engine->newArray(chars.size());
278 for(qsizetype i = 0; i < chars.size(); ++i)
280 auto obj = engine->newObject();
282 "uuid", chars[i].uuid().toString(QUuid::WithoutBraces));
283 obj.setProperty(
"name", chars[i].name());
284 obj.setProperty(
"value", engine->toScriptValue(chars[i].value()));
286 "properties",
static_cast<int>(chars[i].properties()));
287 arr.setProperty(i, obj);
289 onDetailsDiscovered.call({arr});
293 m_service, &QLowEnergyService::characteristicChanged,
this,
294 [
this](
const QLowEnergyCharacteristic& c,
const QByteArray& value) {
295 if(!onCharacteristicChanged.isCallable())
297 auto* engine = qjsEngine(
this);
299 onCharacteristicChanged.call(
300 {c.uuid().toString(QUuid::WithoutBraces),
301 engine->toScriptValue(value)});
305 m_service, &QLowEnergyService::characteristicRead,
this,
306 [
this](
const QLowEnergyCharacteristic& c,
const QByteArray& value) {
307 if(!onCharacteristicRead.isCallable())
309 auto* engine = qjsEngine(
this);
311 onCharacteristicRead.call(
312 {c.uuid().toString(QUuid::WithoutBraces),
313 engine->toScriptValue(value)});
317 m_service, &QLowEnergyService::characteristicWritten,
this,
318 [
this](
const QLowEnergyCharacteristic& c,
const QByteArray& value) {
319 if(!onCharacteristicWritten.isCallable())
321 auto* engine = qjsEngine(
this);
323 onCharacteristicWritten.call(
324 {c.uuid().toString(QUuid::WithoutBraces),
325 engine->toScriptValue(value)});
329 m_service, &QLowEnergyService::errorOccurred,
this,
330 [
this](QLowEnergyService::ServiceError err) {
331 if(!onError.isCallable())
336 case QLowEnergyService::NoError:
338 case QLowEnergyService::OperationError:
339 msg = QStringLiteral(
"Operation error");
341 case QLowEnergyService::CharacteristicWriteError:
342 msg = QStringLiteral(
"Characteristic write error");
344 case QLowEnergyService::DescriptorWriteError:
345 msg = QStringLiteral(
"Descriptor write error");
347 case QLowEnergyService::CharacteristicReadError:
348 msg = QStringLiteral(
"Characteristic read error");
350 case QLowEnergyService::DescriptorReadError:
351 msg = QStringLiteral(
"Descriptor read error");
354 msg = QStringLiteral(
"Unknown service error");
363 return m_service->serviceUuid().toString(QUuid::WithoutBraces);
367 void discoverDetails() { m_service->discoverDetails(); }
368 W_SLOT(discoverDetails)
370 QJSValue characteristics()
372 auto* engine = qjsEngine(
this);
376 auto chars = m_service->characteristics();
377 auto arr = engine->newArray(chars.size());
378 for(qsizetype i = 0; i < chars.size(); ++i)
380 auto obj = engine->newObject();
381 obj.setProperty(
"uuid", chars[i].uuid().toString(QUuid::WithoutBraces));
382 obj.setProperty(
"name", chars[i].name());
383 obj.setProperty(
"value", engine->toScriptValue(chars[i].value()));
384 obj.setProperty(
"properties",
static_cast<int>(chars[i].properties()));
385 arr.setProperty(i, obj);
389 W_SLOT(characteristics)
391 void readCharacteristic(QString uuid)
393 auto c = m_service->characteristic(bleUuidFromString(uuid));
395 return report(QStringLiteral(
"no such characteristic: %1").arg(uuid));
396 m_service->readCharacteristic(c);
398 W_SLOT(readCharacteristic)
400 void writeCharacteristic(QString uuid, QByteArray value)
402 auto c = m_service->characteristic(bleUuidFromString(uuid));
404 return report(QStringLiteral(
"no such characteristic: %1").arg(uuid));
405 m_service->writeCharacteristic(c, value);
407 W_SLOT(writeCharacteristic)
409 void writeCharacteristicNoResponse(QString uuid, QByteArray value)
411 auto c = m_service->characteristic(bleUuidFromString(uuid));
413 return report(QStringLiteral(
"no such characteristic: %1").arg(uuid));
414 if(!c.properties().testFlag(QLowEnergyCharacteristic::WriteNoResponse))
415 report(QStringLiteral(
"%1 does not advertise WriteNoResponse").arg(uuid));
416 m_service->writeCharacteristic(
417 c, value, QLowEnergyService::WriteWithoutResponse);
419 W_SLOT(writeCharacteristicNoResponse)
421 void enableNotifications(QString uuid)
423 auto c = m_service->characteristic(bleUuidFromString(uuid));
425 return report(QStringLiteral(
"no such characteristic: %1").arg(uuid));
426 auto cccd = c.clientCharacteristicConfiguration();
428 return report(QStringLiteral(
429 "%1 has no CCCD descriptor; discoverDetails() must "
430 "have completed before subscribing")
433 if(c.properties().testFlag(QLowEnergyCharacteristic::Indicate))
434 m_service->writeDescriptor(
435 cccd, QLowEnergyCharacteristic::CCCDEnableIndication);
437 m_service->writeDescriptor(
438 cccd, QLowEnergyCharacteristic::CCCDEnableNotification);
440 W_SLOT(enableNotifications)
442 void disableNotifications(QString uuid)
444 auto c = m_service->characteristic(bleUuidFromString(uuid));
446 return report(QStringLiteral(
"no such characteristic: %1").arg(uuid));
447 auto cccd = c.clientCharacteristicConfiguration();
449 m_service->writeDescriptor(cccd, QLowEnergyCharacteristic::CCCDDisable);
451 W_SLOT(disableNotifications)
453 QJSValue onDetailsDiscovered;
454 QJSValue onCharacteristicRead;
455 QJSValue onCharacteristicWritten;
456 QJSValue onCharacteristicChanged;
459 W_PROPERTY(QJSValue, onDetailsDiscovered MEMBER onDetailsDiscovered)
460 W_PROPERTY(QJSValue, onCharacteristicRead MEMBER onCharacteristicRead)
461 W_PROPERTY(QJSValue, onCharacteristicWritten MEMBER onCharacteristicWritten)
462 W_PROPERTY(QJSValue, onCharacteristicChanged MEMBER onCharacteristicChanged)
463 W_PROPERTY(QJSValue, onError MEMBER onError)
466 void report(
const QString& msg)
469 if(onError.isCallable())
473 QLowEnergyService* m_service{};
476class qml_ble_controller
478 ,
public Nano::Observer
480 W_OBJECT(qml_ble_controller)
484 std::atomic_bool alive{
true};
487 explicit qml_ble_controller(
const QBluetoothDeviceInfo& info)
488 : m_state{std::make_shared<state>()}
491 m_controller = QLowEnergyController::createCentral(m_deviceInfo,
this);
495 m_controller, &QLowEnergyController::connected,
this, [
this, st]() {
499 if(!onConnected.isCallable())
501 auto* engine = qjsEngine(
this);
504 onConnected.call({engine->newQObject(
this)});
508 m_controller, &QLowEnergyController::disconnected,
this, [
this, st]() {
512 if(onDisconnected.isCallable())
513 onDisconnected.call();
517 m_controller, &QLowEnergyController::serviceDiscovered,
this,
518 [
this, st](
const QBluetoothUuid& uuid) {
521 if(onServiceDiscovered.isCallable())
522 onServiceDiscovered.call({uuid.toString(QUuid::WithoutBraces)});
526 m_controller, &QLowEnergyController::discoveryFinished,
this,
530 if(!onDiscoveryFinished.isCallable())
532 auto* engine = qjsEngine(
this);
535 onDiscoveryFinished.call({engine->newQObject(
this)});
539 m_controller, &QLowEnergyController::mtuChanged,
this,
540 [
this, st](
int mtu) {
543 if(onMtuChanged.isCallable())
544 onMtuChanged.call({mtu});
548 m_controller, &QLowEnergyController::errorOccurred,
this,
549 [
this, st](QLowEnergyController::Error) {
552 if(onError.isCallable())
553 onError.call({m_controller->errorString()});
557 ~qml_ble_controller()
559 m_state->alive =
false;
563 void open() { m_controller->connectToDevice(); }
570 m_controller->disconnectFromDevice();
575 void discoverServices() { m_controller->discoverServices(); }
576 W_SLOT(discoverServices)
578 QObject* service(QString uuid)
580 auto serviceUuid = bleUuidFromString(uuid);
581 auto* svc = m_controller->createServiceObject(serviceUuid,
this);
585 auto* wrapper =
new qml_ble_service(svc,
this);
586 auto* engine = qjsEngine(
this);
588 engine->newQObject(wrapper);
595 auto* engine = qjsEngine(
this);
599 auto uuids = m_controller->services();
600 auto arr = engine->newArray(uuids.size());
601 for(qsizetype i = 0; i < uuids.size(); ++i)
602 arr.setProperty(i, uuids[i].toString(QUuid::WithoutBraces));
607 int mtu()
const {
return m_controller->mtu(); }
610 QString remoteName()
const {
return m_controller->remoteName(); }
613 QJSValue onConnected;
614 QJSValue onDisconnected;
615 QJSValue onServiceDiscovered;
616 QJSValue onDiscoveryFinished;
617 QJSValue onMtuChanged;
621 std::shared_ptr<state> m_state;
622 QLowEnergyController* m_controller{};
623 QBluetoothDeviceInfo m_deviceInfo;
624 std::atomic_bool m_is_open{
false};
Definition qml_device.cpp:43
spdlog::logger & logger() noexcept
Where the errors will be logged. Default is stderr.
Definition context.cpp:120