3#if __has_include(<QBluetoothDeviceDiscoveryAgent>)
4#define OSSIA_HAS_BLUETOOTH 1
6#include <ossia-qt/protocols/utils.hpp>
8#include <QBluetoothDeviceDiscoveryAgent>
9#include <QBluetoothDeviceInfo>
10#include <QBluetoothSocket>
11#include <QBluetoothUuid>
17#include <QLowEnergyCharacteristic>
18#include <QLowEnergyController>
19#include <QLowEnergyDescriptor>
20#include <QLowEnergyService>
22#include <nano_observer.hpp>
29static QBluetoothUuid bleUuidFromString(
const QString& str)
34 auto val = str.toUInt(&ok, 16);
36 return QBluetoothUuid{
static_cast<quint16
>(val)};
38 return QBluetoothUuid{QUuid::fromString(str)};
41class qml_bluetooth_scanner
43 ,
public Nano::Observer
45 W_OBJECT(qml_bluetooth_scanner)
47 qml_bluetooth_scanner()
49 m_agent =
new QBluetoothDeviceDiscoveryAgent(
this);
52 m_agent, &QBluetoothDeviceDiscoveryAgent::deviceDiscovered,
this,
53 [
this](
const QBluetoothDeviceInfo& info) {
54 if(!onDeviceDiscovered.isCallable())
56 auto* engine = qjsEngine(
this);
60 auto dev = engine->newObject();
61 dev.setProperty(
"Name", info.name());
62 dev.setProperty(
"Address", info.address().toString());
64 "DeviceUuid", info.deviceUuid().toString(QUuid::WithoutBraces));
65 dev.setProperty(
"RSSI", info.rssi());
68 info.coreConfigurations().testFlag(
69 QBluetoothDeviceInfo::LowEnergyCoreConfiguration));
72 info.coreConfigurations().testFlag(
73 QBluetoothDeviceInfo::BaseRateCoreConfiguration));
76 static_cast<int>(info.majorDeviceClass()));
77 dev.setProperty(
"MinorDeviceClass", info.minorDeviceClass());
79 auto uuids = info.serviceUuids();
80 auto serviceArr = engine->newArray(uuids.size());
81 for(qsizetype i = 0; i < uuids.size(); ++i)
82 serviceArr.setProperty(i, uuids[i].toString(QUuid::WithoutBraces));
83 dev.setProperty(
"ServiceUuids", serviceArr);
85 auto mfData = info.manufacturerData();
88 auto mfObj = engine->newObject();
89 for(
auto it = mfData.begin(); it != mfData.end(); ++it)
91 QString::number(it.key()), engine->toScriptValue(it.value()));
92 dev.setProperty(
"ManufacturerData", mfObj);
95 onDeviceDiscovered.call({dev});
99 m_agent, &QBluetoothDeviceDiscoveryAgent::finished,
this, [
this]() {
100 if(onFinished.isCallable())
105 m_agent, &QBluetoothDeviceDiscoveryAgent::errorOccurred,
this,
106 [
this](QBluetoothDeviceDiscoveryAgent::Error) {
107 if(onError.isCallable())
108 onError.call({m_agent->errorString()});
112 ~qml_bluetooth_scanner() { stop(); }
117 QBluetoothDeviceDiscoveryAgent::ClassicMethod
118 | QBluetoothDeviceDiscoveryAgent::LowEnergyMethod);
124 m_agent->start(QBluetoothDeviceDiscoveryAgent::LowEnergyMethod);
130 m_agent->start(QBluetoothDeviceDiscoveryAgent::ClassicMethod);
134 void stop() { m_agent->stop(); }
137 void setLowEnergyDiscoveryTimeout(
int msTimeout)
139 m_agent->setLowEnergyDiscoveryTimeout(msTimeout);
141 W_SLOT(setLowEnergyDiscoveryTimeout)
143 QJSValue onDeviceDiscovered;
148 QBluetoothDeviceDiscoveryAgent* m_agent{};
151class qml_bluetooth_socket
153 ,
public Nano::Observer
155 W_OBJECT(qml_bluetooth_socket)
159 std::atomic_bool alive{
true};
162 qml_bluetooth_socket(
163 const QBluetoothAddress& address,
const QBluetoothUuid& serviceUuid)
164 : m_state{std::make_shared<state>()}
166 , m_serviceUuid{serviceUuid}
168 m_socket =
new QBluetoothSocket(QBluetoothServiceInfo::RfcommProtocol,
this);
172 m_socket, &QBluetoothSocket::connected,
this, [
this, st]() {
176 if(!onOpen.isCallable())
178 auto* engine = qjsEngine(
this);
181 onOpen.call({engine->newQObject(
this)});
185 m_socket, &QBluetoothSocket::disconnected,
this, [
this, st]() {
189 if(onClose.isCallable())
194 m_socket, &QBluetoothSocket::readyRead,
this, [
this, st]() {
197 if(!onMessage.isCallable())
199 auto data = m_socket->readAll();
200 auto* engine = qjsEngine(
this);
202 onMessage.call({engine->toScriptValue(data)});
206 m_socket, &QBluetoothSocket::errorOccurred,
this,
207 [
this, st](QBluetoothSocket::SocketError) {
210 if(onError.isCallable())
211 onError.call({m_socket->errorString()});
215 ~qml_bluetooth_socket()
217 m_state->alive =
false;
221 void open() { m_socket->connectToService(m_address, m_serviceUuid); }
223 void write(QByteArray data)
226 m_socket->write(data);
235 m_socket->disconnectFromService();
246 std::shared_ptr<state> m_state;
247 QBluetoothSocket* m_socket{};
248 QBluetoothAddress m_address;
249 QBluetoothUuid m_serviceUuid;
250 std::atomic_bool m_is_open{
false};
255 ,
public Nano::Observer
257 W_OBJECT(qml_ble_service)
259 explicit qml_ble_service(QLowEnergyService* service, QObject* parent)
264 m_service, &QLowEnergyService::stateChanged,
this,
265 [
this](QLowEnergyService::ServiceState state) {
266 if(state != QLowEnergyService::RemoteServiceDiscovered)
268 if(!onDetailsDiscovered.isCallable())
270 auto* engine = qjsEngine(
this);
274 auto chars = m_service->characteristics();
275 auto arr = engine->newArray(chars.size());
276 for(qsizetype i = 0; i < chars.size(); ++i)
278 auto obj = engine->newObject();
280 "uuid", chars[i].uuid().toString(QUuid::WithoutBraces));
281 obj.setProperty(
"name", chars[i].name());
282 obj.setProperty(
"value", engine->toScriptValue(chars[i].value()));
284 "properties",
static_cast<int>(chars[i].properties()));
285 arr.setProperty(i, obj);
287 onDetailsDiscovered.call({arr});
291 m_service, &QLowEnergyService::characteristicChanged,
this,
292 [
this](
const QLowEnergyCharacteristic& c,
const QByteArray& value) {
293 if(!onCharacteristicChanged.isCallable())
295 auto* engine = qjsEngine(
this);
297 onCharacteristicChanged.call(
298 {c.uuid().toString(QUuid::WithoutBraces),
299 engine->toScriptValue(value)});
303 m_service, &QLowEnergyService::characteristicRead,
this,
304 [
this](
const QLowEnergyCharacteristic& c,
const QByteArray& value) {
305 if(!onCharacteristicRead.isCallable())
307 auto* engine = qjsEngine(
this);
309 onCharacteristicRead.call(
310 {c.uuid().toString(QUuid::WithoutBraces),
311 engine->toScriptValue(value)});
315 m_service, &QLowEnergyService::characteristicWritten,
this,
316 [
this](
const QLowEnergyCharacteristic& c,
const QByteArray& value) {
317 if(!onCharacteristicWritten.isCallable())
319 auto* engine = qjsEngine(
this);
321 onCharacteristicWritten.call(
322 {c.uuid().toString(QUuid::WithoutBraces),
323 engine->toScriptValue(value)});
327 m_service, &QLowEnergyService::errorOccurred,
this,
328 [
this](QLowEnergyService::ServiceError err) {
329 if(!onError.isCallable())
334 case QLowEnergyService::NoError:
336 case QLowEnergyService::OperationError:
337 msg = QStringLiteral(
"Operation error");
339 case QLowEnergyService::CharacteristicWriteError:
340 msg = QStringLiteral(
"Characteristic write error");
342 case QLowEnergyService::DescriptorWriteError:
343 msg = QStringLiteral(
"Descriptor write error");
345 case QLowEnergyService::CharacteristicReadError:
346 msg = QStringLiteral(
"Characteristic read error");
348 case QLowEnergyService::DescriptorReadError:
349 msg = QStringLiteral(
"Descriptor read error");
352 msg = QStringLiteral(
"Unknown service error");
361 return m_service->serviceUuid().toString(QUuid::WithoutBraces);
365 void discoverDetails() { m_service->discoverDetails(); }
366 W_SLOT(discoverDetails)
368 QJSValue characteristics()
370 auto* engine = qjsEngine(
this);
374 auto chars = m_service->characteristics();
375 auto arr = engine->newArray(chars.size());
376 for(qsizetype i = 0; i < chars.size(); ++i)
378 auto obj = engine->newObject();
379 obj.setProperty(
"uuid", chars[i].uuid().toString(QUuid::WithoutBraces));
380 obj.setProperty(
"name", chars[i].name());
381 obj.setProperty(
"value", engine->toScriptValue(chars[i].value()));
382 obj.setProperty(
"properties",
static_cast<int>(chars[i].properties()));
383 arr.setProperty(i, obj);
387 W_SLOT(characteristics)
389 void readCharacteristic(QString uuid)
391 auto c = m_service->characteristic(bleUuidFromString(uuid));
393 m_service->readCharacteristic(c);
395 W_SLOT(readCharacteristic)
397 void writeCharacteristic(QString uuid, QByteArray value)
399 auto c = m_service->characteristic(bleUuidFromString(uuid));
401 m_service->writeCharacteristic(c, value);
403 W_SLOT(writeCharacteristic)
405 void writeCharacteristicNoResponse(QString uuid, QByteArray value)
407 auto c = m_service->characteristic(bleUuidFromString(uuid));
409 m_service->writeCharacteristic(
410 c, value, QLowEnergyService::WriteWithoutResponse);
412 W_SLOT(writeCharacteristicNoResponse)
414 void enableNotifications(QString uuid)
416 auto c = m_service->characteristic(bleUuidFromString(uuid));
419 auto cccd = c.clientCharacteristicConfiguration();
423 if(c.properties().testFlag(QLowEnergyCharacteristic::Indicate))
424 m_service->writeDescriptor(
425 cccd, QLowEnergyCharacteristic::CCCDEnableIndication);
427 m_service->writeDescriptor(
428 cccd, QLowEnergyCharacteristic::CCCDEnableNotification);
430 W_SLOT(enableNotifications)
432 void disableNotifications(QString uuid)
434 auto c = m_service->characteristic(bleUuidFromString(uuid));
437 auto cccd = c.clientCharacteristicConfiguration();
439 m_service->writeDescriptor(cccd, QLowEnergyCharacteristic::CCCDDisable);
441 W_SLOT(disableNotifications)
443 QJSValue onDetailsDiscovered;
444 QJSValue onCharacteristicRead;
445 QJSValue onCharacteristicWritten;
446 QJSValue onCharacteristicChanged;
450 QLowEnergyService* m_service{};
453class qml_ble_controller
455 ,
public Nano::Observer
457 W_OBJECT(qml_ble_controller)
461 std::atomic_bool alive{
true};
464 explicit qml_ble_controller(
const QBluetoothDeviceInfo& info)
465 : m_state{std::make_shared<state>()}
468 m_controller = QLowEnergyController::createCentral(m_deviceInfo,
this);
472 m_controller, &QLowEnergyController::connected,
this, [
this, st]() {
476 if(!onConnected.isCallable())
478 auto* engine = qjsEngine(
this);
481 onConnected.call({engine->newQObject(
this)});
485 m_controller, &QLowEnergyController::disconnected,
this, [
this, st]() {
489 if(onDisconnected.isCallable())
490 onDisconnected.call();
494 m_controller, &QLowEnergyController::serviceDiscovered,
this,
495 [
this, st](
const QBluetoothUuid& uuid) {
498 if(onServiceDiscovered.isCallable())
499 onServiceDiscovered.call({uuid.toString(QUuid::WithoutBraces)});
503 m_controller, &QLowEnergyController::discoveryFinished,
this,
507 if(!onDiscoveryFinished.isCallable())
509 auto* engine = qjsEngine(
this);
512 onDiscoveryFinished.call({engine->newQObject(
this)});
516 m_controller, &QLowEnergyController::mtuChanged,
this,
517 [
this, st](
int mtu) {
520 if(onMtuChanged.isCallable())
521 onMtuChanged.call({mtu});
525 m_controller, &QLowEnergyController::errorOccurred,
this,
526 [
this, st](QLowEnergyController::Error) {
529 if(onError.isCallable())
530 onError.call({m_controller->errorString()});
534 ~qml_ble_controller()
536 m_state->alive =
false;
540 void open() { m_controller->connectToDevice(); }
547 m_controller->disconnectFromDevice();
552 void discoverServices() { m_controller->discoverServices(); }
553 W_SLOT(discoverServices)
555 QObject* service(QString uuid)
557 auto serviceUuid = bleUuidFromString(uuid);
558 auto* svc = m_controller->createServiceObject(serviceUuid,
this);
562 auto* wrapper =
new qml_ble_service(svc,
this);
563 auto* engine = qjsEngine(
this);
565 engine->newQObject(wrapper);
572 auto* engine = qjsEngine(
this);
576 auto uuids = m_controller->services();
577 auto arr = engine->newArray(uuids.size());
578 for(qsizetype i = 0; i < uuids.size(); ++i)
579 arr.setProperty(i, uuids[i].toString(QUuid::WithoutBraces));
584 int mtu()
const {
return m_controller->mtu(); }
587 QString remoteName()
const {
return m_controller->remoteName(); }
590 QJSValue onConnected;
591 QJSValue onDisconnected;
592 QJSValue onServiceDiscovered;
593 QJSValue onDiscoveryFinished;
594 QJSValue onMtuChanged;
598 std::shared_ptr<state> m_state;
599 QLowEnergyController* m_controller{};
600 QBluetoothDeviceInfo m_deviceInfo;
601 std::atomic_bool m_is_open{
false};
Definition qml_device.cpp:43