QML UI: move BT handling into core code

This shouldn't be part of the UI (qmlmanager), but part of our
overall handling of dive computers and BT devices.

Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
This commit is contained in:
Dirk Hohndel 2017-06-05 19:41:57 -07:00
parent 3b993fbaad
commit b14a522f4f
8 changed files with 305 additions and 199 deletions

View file

@ -26,6 +26,7 @@ endif()
# compile the core library, in C.
set(SUBSURFACE_CORE_LIB_SRCS
btdiscovery.cpp
cochran.c
datatrak.c
deco.c

165
core/btdiscovery.cpp Normal file
View file

@ -0,0 +1,165 @@
// SPDX-License-Identifier: GPL-2.0
#include "btdiscovery.h"
#include "downloadfromdcthread.h"
#include <QDebug>
BTDiscovery *BTDiscovery::m_instance = NULL;
BTDiscovery::BTDiscovery(QObject *parent)
{
Q_UNUSED(parent)
if (m_instance) {
qDebug() << "trying to create an additional BTDiscovery object";
return;
}
m_instance = this;
#if defined(BT_SUPPORT)
if (localBtDevice.isValid() &&
localBtDevice.hostMode() == QBluetoothLocalDevice::HostConnectable) {
btPairedDevices.clear();
qDebug() << "localDevice " + localBtDevice.name() + " is valid, starting discovery";
#if defined(Q_OS_LINUX)
discoveryAgent = new QBluetoothDeviceDiscoveryAgent(this);
connect(discoveryAgent, &QBluetoothDeviceDiscoveryAgent::deviceDiscovered, this, &BTDiscovery::btDeviceDiscovered);
discoveryAgent->start();
#endif
#if defined(Q_OS_ANDROID) && defined(BT_SUPPORT)
getBluetoothDevices();
#endif
for (int i = 0; i < btPairedDevices.length(); i++) {
qDebug() << "Paired =" << btPairedDevices[i].name << btPairedDevices[i].address.toString();
}
#if defined(Q_OS_LINUX)
discoveryAgent->stop();
#endif
} else {
qDebug() << "localBtDevice isn't valid";
}
#endif
}
BTDiscovery::~BTDiscovery()
{
m_instance = NULL;
#if defined(BT_SUPPORT)
free(discoveryAgent);
#endif
}
BTDiscovery *BTDiscovery::instance()
{
if (!m_instance)
m_instance = new BTDiscovery();
return m_instance;
}
#if defined(BT_SUPPORT)
extern void addBtUuid(QBluetoothUuid uuid);
extern QHash<QString, QStringList> productList;
extern QStringList vendorList;
void BTDiscovery::btDeviceDiscovered(const QBluetoothDeviceInfo &device)
{
btPairedDevice this_d;
this_d.address = device.address();
this_d.name = device.name();
btPairedDevices.append(this_d);
QString newDevice = device.name();
// all the HW OSTC BT computers show up as "OSTC" + some other text, depending on model
if (newDevice.startsWith("OSTC"))
newDevice = "OSTC 3";
QList<QBluetoothUuid> serviceUuids = device.serviceUuids();
foreach (QBluetoothUuid id, serviceUuids) {
addBtUuid(id);
qDebug() << id.toByteArray();
}
qDebug() << "Found new device " + newDevice + " (" + device.address().toString() + ")";
QString vendor, product;
foreach (vendor, productList.keys()) {
if (productList[vendor].contains(newDevice)) {
qDebug() << "this could be a " + vendor + " " +
(newDevice == "OSTC 3" ? "OSTC family" : newDevice);
struct btVendorProduct btVP;
btVP.btdi = device;
btVP.vendorIdx = vendorList.indexOf(vendor);
btVP.productIdx = productList[vendor].indexOf(newDevice);
qDebug() << "adding new btDCs entry" << newDevice << btVP.vendorIdx << btVP.productIdx;
btDCs << btVP;
}
}
}
QList <struct btVendorProduct> BTDiscovery::getBtDcs()
{
return btDCs;
}
// Android: As Qt is not able to pull the pairing data from a device, i
// a lengthy discovery process is needed to see what devices are paired. On
// https://forum.qt.io/topic/46075/solved-bluetooth-list-paired-devices
// user s.frings74 does, however, present a solution to this using JNI.
// Currently, this code is taken "as is".
#if defined(Q_OS_ANDROID)
void BTDiscovery::getBluetoothDevices()
{
struct BTDiscovery::btPairedDevice result;
// Query via Android Java API.
// returns a BluetoothAdapter
QAndroidJniObject adapter=QAndroidJniObject::callStaticObjectMethod("android/bluetooth/BluetoothAdapter","getDefaultAdapter","()Landroid/bluetooth/BluetoothAdapter;");
if (checkException("BluetoothAdapter.getDefaultAdapter()", &adapter)) {
return;
}
// returns a Set<BluetoothDevice>
QAndroidJniObject pairedDevicesSet=adapter.callObjectMethod("getBondedDevices","()Ljava/util/Set;");
if (checkException("BluetoothAdapter.getBondedDevices()", &pairedDevicesSet)) {
return;
}
jint size=pairedDevicesSet.callMethod<jint>("size");
checkException("Set<BluetoothDevice>.size()", &pairedDevicesSet);
if (size > 0) {
// returns an Iterator<BluetoothDevice>
QAndroidJniObject iterator=pairedDevicesSet.callObjectMethod("iterator","()Ljava/util/Iterator;");
if (checkException("Set<BluetoothDevice>.iterator()", &iterator)) {
return;
}
for (int i = 0; i < size; i++) {
// returns a BluetoothDevice
QAndroidJniObject dev=iterator.callObjectMethod("next","()Ljava/lang/Object;");
if (checkException("Iterator<BluetoothDevice>.next()", &dev)) {
continue;
}
result.address = QBluetoothAddress(dev.callObjectMethod("getAddress","()Ljava/lang/String;").toString());
result.name = dev.callObjectMethod("getName", "()Ljava/lang/String;").toString();
btPairedDevices.append(result);
}
}
}
bool BTDiscovery::checkException(const char* method, const QAndroidJniObject *obj)
{
static QAndroidJniEnvironment env;
bool result = false;
if (env->ExceptionCheck()) {
qCritical("Exception in %s", method);
env->ExceptionDescribe();
env->ExceptionClear();
result=true;
}
if (!(obj == NULL || obj->isValid())) {
qCritical("Invalid object returned by %s", method);
result=true;
}
return result;
}
#endif // Q_OS_ANDROID
#endif // BT_SUPPORT

64
core/btdiscovery.h Normal file
View file

@ -0,0 +1,64 @@
// SPDX-License-Identifier: GPL-2.0
#ifndef BTDISCOVERY_H
#define BTDISCOVERY_H
#include <QObject>
#include <QString>
#include <QLoggingCategory>
#if defined(BT_SUPPORT)
#include <QBluetoothLocalDevice>
#include <QBluetoothDeviceDiscoveryAgent>
#include <QBluetoothUuid>
struct btVendorProduct {
QBluetoothDeviceInfo btdi;
int vendorIdx;
int productIdx;
};
#endif
#if defined(Q_OS_ANDROID)
#include <QAndroidJniObject>
#endif
class BTDiscovery : public QObject {
Q_OBJECT
public:
BTDiscovery(QObject *parent = NULL);
~BTDiscovery();
static BTDiscovery *instance();
#if defined(BT_SUPPORT)
struct btPairedDevice {
QBluetoothAddress address;
QString name;
};
void btDeviceDiscovered(const QBluetoothDeviceInfo &device);
#if defined(Q_OS_ANDROID)
void getBluetoothDevices();
#endif
QList<struct btVendorProduct> getBtDcs();
#endif
private:
static BTDiscovery *m_instance;
#if defined(BT_SUPPORT)
QList<struct btVendorProduct> btDCs;
#endif
#if defined(Q_OS_ANDROID)
bool checkException(const char* method, const QAndroidJniObject* obj);
#endif
#if defined(BT_SUPPORT)
QList<struct btPairedDevice> btPairedDevices;
QBluetoothLocalDevice localBtDevice;
QBluetoothDeviceDiscoveryAgent *discoveryAgent;
#endif
signals:
void dcVendorChanged();
void dcProductChanged();
void dcBtChanged();
};
#endif // BTDISCOVERY_H

View file

@ -99,8 +99,15 @@ void fill_computer_list()
#endif
}
DCDeviceData *DCDeviceData::m_instance = NULL;
DCDeviceData::DCDeviceData(QObject *parent) : QObject(parent)
{
if (m_instance) {
qDebug() << "already have an instance of DCDevieData";
return;
}
m_instance = this;
memset(&data, 0, sizeof(data));
data.trip = nullptr;
data.download_table = nullptr;
@ -108,6 +115,18 @@ DCDeviceData::DCDeviceData(QObject *parent) : QObject(parent)
data.deviceid = 0;
}
DCDeviceData *DCDeviceData::instance()
{
if (!m_instance)
m_instance = new DCDeviceData();
return m_instance;
}
QStringList DCDeviceData::getProductListFromVendor(const QString &vendor)
{
return productList[vendor];
}
DCDeviceData * DownloadThread::data()
{
return m_data;
@ -222,3 +241,40 @@ device_data_t* DCDeviceData::internalData()
{
return &data;
}
int DCDeviceData::getDetectedVendorIndex()
{
#if defined(BT_SUPPORT)
QList<btVendorProduct> btDCs = BTDiscovery::instance()->getBtDcs();
if (!btDCs.isEmpty()) {
qDebug() << "getVendorIdx" << btDCs.first().vendorIdx;
return btDCs.first().vendorIdx;
}
#endif
return -1;
}
int DCDeviceData::getDetectedProductIndex()
{
#if defined(BT_SUPPORT)
QList<btVendorProduct> btDCs = BTDiscovery::instance()->getBtDcs();
if (!btDCs.isEmpty()) {
qDebug() << "getProductIdx" << btDCs.first().productIdx;
return btDCs.first().productIdx;
}
#endif
return -1;
}
QString DCDeviceData::getDetectedDeviceAddress()
{
#if BT_SUPPORT
QList<btVendorProduct> btDCs = BTDiscovery::instance()->getBtDcs();
if (!btDCs.isEmpty()) {
QString btAddr = btDCs.first().btdi.address().toString();
qDebug() << "getBtAddress" << btAddr;
return btAddr;
}
return QString();
#endif
}

View file

@ -4,9 +4,11 @@
#include <QThread>
#include <QMap>
#include <QHash>
#include <QLoggingCategory>
#include "dive.h"
#include "libdivecomputer.h"
#include "core/btdiscovery.h"
/* Helper object for access of Device Data in QML */
class DCDeviceData : public QObject {
@ -25,6 +27,7 @@ class DCDeviceData : public QObject {
public:
DCDeviceData(QObject *parent = nullptr);
static DCDeviceData *instance();
QString vendor() const;
QString product() const;
@ -41,6 +44,11 @@ public:
/* this needs to be a pointer to make the C-API happy */
device_data_t* internalData();
Q_INVOKABLE QStringList getProductListFromVendor(const QString& vendor);
Q_INVOKABLE int getDetectedVendorIndex();
Q_INVOKABLE int getDetectedProductIndex();
Q_INVOKABLE QString getDetectedDeviceAddress();
public slots:
void setVendor(const QString& vendor);
void setProduct(const QString& product);
@ -53,6 +61,7 @@ public slots:
void setSaveDump(bool dumpMode);
void setSaveLog(bool saveLog);
private:
static DCDeviceData *m_instance;
device_data_t data;
};
@ -64,7 +73,7 @@ public:
DownloadThread();
void run() override;
DCDeviceData *data();
Q_INVOKABLE DCDeviceData *data();
QString error;
private:

View file

@ -57,11 +57,11 @@ Kirigami.Page {
id: comboVendor
Layout.fillWidth: true
model: vendorList
currentIndex: manager.getVendorIndex()
currentIndex: downloadThread.data().getDetectedVendorIndex()
onCurrentTextChanged: {
comboProduct.model = manager.getDCListFromVendor(comboVendor.currentText)
if (currentIndex == manager.getVendorIndex())
comboProduct.currentIndex = manager.getProductIndex()
comboProduct.model = downloadThread.data().getProductListFromVendor(comboVendor.currentText)
if (currentIndex == downloadThread.data().getDetectedVendorIndex())
comboProduct.currentIndex = downloadThread.data().getDetectedProductIndex()
}
}
Kirigami.Label { text: qsTr(" Dive Computer:") }
@ -74,7 +74,7 @@ Kirigami.Page {
Kirigami.Label { text: qsTr("Bluetooth download:") }
CheckBox {
id: isBluetooth
checked: manager.getVendorIndex() != -1
checked: downloadThread.data().getDetectedVendorIndex() != -1
}
}

View file

@ -94,28 +94,9 @@ QMLManager::QMLManager() : m_locationServiceEnabled(false),
alreadySaving(false)
{
#if defined(BT_SUPPORT)
if (localBtDevice.isValid() &&
localBtDevice.hostMode() == QBluetoothLocalDevice::HostConnectable) {
btPairedDevices.clear();
QString localDeviceName = "localDevice " + localBtDevice.name() + " is valid, starting discovery";
appendTextToLog(localDeviceName.toUtf8().data());
#if defined(Q_OS_LINUX)
discoveryAgent = new QBluetoothDeviceDiscoveryAgent(this);
connect(discoveryAgent, &QBluetoothDeviceDiscoveryAgent::deviceDiscovered, this, &QMLManager::btDeviceDiscovered);
discoveryAgent->start();
#endif
#if defined(Q_OS_ANDROID)
getBluetoothDevices();
#endif
for (int i = 0; i < btPairedDevices.length(); i++) {
qDebug() << "Paired =" << btPairedDevices[i].name << btPairedDevices[i].address.toString();
}
#if defined(Q_OS_LINUX)
discoveryAgent->stop();
#endif
} else {
appendTextToLog("localBtDevice isn't valid");
}
// ensure that we start the BTDiscovery - this should be triggered by the export of the class
// to QML, but that doesn't seem to always work
BTDiscovery *btDiscovery = BTDiscovery::instance();
#endif
m_instance = this;
m_lastDevicePixelRatio = qApp->devicePixelRatio();
@ -217,78 +198,6 @@ void QMLManager::mergeLocalRepo()
process_dives(true, false);
}
#if defined(BT_SUPPORT)
extern void addBtUuid(QBluetoothUuid uuid);
void QMLManager::btDeviceDiscovered(const QBluetoothDeviceInfo &device)
{
btPairedDevice this_d;
this_d.address = device.address();
this_d.name = device.name();
btPairedDevices.append(this_d);
QString newDevice = device.name();
// all the HW OSTC BT computers show up as "OSTC" + some other text, depending on model
if (newDevice.startsWith("OSTC"))
newDevice = "OSTC 3";
QList<QBluetoothUuid> serviceUuids = device.serviceUuids();
foreach (QBluetoothUuid id, serviceUuids) {
addBtUuid(id);
qDebug() << id.toByteArray();
}
appendTextToLog("Found new device " + newDevice + " (" + device.address().toString() + ")");
QString vendor, product;
foreach (vendor, productList.keys()) {
if (productList[vendor].contains(newDevice)) {
appendTextToLog("this could be a " + vendor + " " +
(newDevice == "OSTC 3" ? "OSTC family" : newDevice));
struct btVendorProduct btVP;
btVP.btdi = device;
btVP.vendorIdx = vendorList.indexOf(vendor);
btVP.productIdx = productList[vendor].indexOf(newDevice);
qDebug() << "adding new btDCs entry" << newDevice << btVP.vendorIdx << btVP.productIdx;
btDCs << btVP;
}
}
}
#endif
int QMLManager::getVendorIndex()
{
#if defined(BT_SUPPORT)
if (!btDCs.isEmpty()) {
qDebug() << "getVendorIdx" << btDCs.first().vendorIdx;
return btDCs.first().vendorIdx;
}
#endif
return -1;
}
int QMLManager::getProductIndex()
{
#if defined(BT_SUPPORT)
if (!btDCs.isEmpty()) {
qDebug() << "getProductIdx" << btDCs.first().productIdx;
return btDCs.first().productIdx;
}
#endif
return -1;
}
QString QMLManager::getBtAddress()
{
#if BT_SUPPORT
if (!btDCs.isEmpty()) {
QString btAddr = btDCs.first().btdi.address().toString();
qDebug() << "getBtAddress" << btAddr;
return btAddr;
}
return QString();
#endif
}
void QMLManager::finishSetup()
{
// Initialize cloud credentials.
@ -1611,74 +1520,3 @@ void QMLManager::setShowPin(bool enable)
m_showPin = enable;
emit showPinChanged();
}
QStringList QMLManager::getDCListFromVendor(const QString& vendor)
{
return productList[vendor];
}
// Android: As Qt is not able to pull the pairing data from a device, i
// a lengthy discovery process is needed to see what devices are paired. On
// https://forum.qt.io/topic/46075/solved-bluetooth-list-paired-devices
// user s.frings74 does, however, present a solution to this using JNI.
// Currently, this code is taken "as is".
void QMLManager::getBluetoothDevices()
{
#if defined(Q_OS_ANDROID) && defined(BT_SUPPORT)
struct QMLManager::btPairedDevice result;
// Query via Android Java API.
// returns a BluetoothAdapter
QAndroidJniObject adapter=QAndroidJniObject::callStaticObjectMethod("android/bluetooth/BluetoothAdapter","getDefaultAdapter","()Landroid/bluetooth/BluetoothAdapter;");
if (checkException("BluetoothAdapter.getDefaultAdapter()", &adapter)) {
return;
}
// returns a Set<BluetoothDevice>
QAndroidJniObject pairedDevicesSet=adapter.callObjectMethod("getBondedDevices","()Ljava/util/Set;");
if (checkException("BluetoothAdapter.getBondedDevices()", &pairedDevicesSet)) {
return;
}
jint size=pairedDevicesSet.callMethod<jint>("size");
checkException("Set<BluetoothDevice>.size()", &pairedDevicesSet);
if (size > 0) {
// returns an Iterator<BluetoothDevice>
QAndroidJniObject iterator=pairedDevicesSet.callObjectMethod("iterator","()Ljava/util/Iterator;");
if (checkException("Set<BluetoothDevice>.iterator()", &iterator)) {
return;
}
for (int i = 0; i < size; i++) {
// returns a BluetoothDevice
QAndroidJniObject dev=iterator.callObjectMethod("next","()Ljava/lang/Object;");
if (checkException("Iterator<BluetoothDevice>.next()", &dev)) {
continue;
}
result.address = QBluetoothAddress(dev.callObjectMethod("getAddress","()Ljava/lang/String;").toString());
result.name = dev.callObjectMethod("getName", "()Ljava/lang/String;").toString();
btPairedDevices.append(result);
}
}
#endif
}
#if defined(Q_OS_ANDROID)
bool QMLManager::checkException(const char* method, const QAndroidJniObject *obj)
{
static QAndroidJniEnvironment env;
bool result = false;
if (env->ExceptionCheck()) {
qCritical("Exception in %s", method);
env->ExceptionDescribe();
env->ExceptionClear();
result=true;
}
if (!(obj == NULL || obj->isValid())) {
qCritical("Invalid object returned by %s", method);
result=true;
}
return result;
}
#endif

View file

@ -16,6 +16,7 @@
#include <QAndroidJniObject>
#endif
#include "core/btdiscovery.h"
#include "core/gpslocation.h"
#include "qt-models/divelistmodel.h"
@ -121,18 +122,6 @@ public:
QStringList cylinderInit() const;
bool showPin() const;
void setShowPin(bool enable);
Q_INVOKABLE QStringList getDCListFromVendor(const QString& vendor);
Q_INVOKABLE int getVendorIndex();
Q_INVOKABLE int getProductIndex();
Q_INVOKABLE QString getBtAddress();
#if defined(BT_SUPPORT)
struct btPairedDevice {
QBluetoothAddress address;
QString name;
};
void btDeviceDiscovered(const QBluetoothDeviceInfo &device);
void getBluetoothDevices();
#endif
public slots:
void applicationStateChanged(Qt::ApplicationState state);
@ -216,22 +205,6 @@ private:
bool currentGitLocalOnly;
bool m_showPin;
#if defined(Q_OS_ANDROID)
bool checkException(const char* method, const QAndroidJniObject* obj);
#endif
#if defined(BT_SUPPORT)
QList<struct btPairedDevice> btPairedDevices;
QBluetoothLocalDevice localBtDevice;
QBluetoothDeviceDiscoveryAgent *discoveryAgent;
struct btVendorProduct {
QBluetoothDeviceInfo btdi;
int vendorIdx;
int productIdx;
};
QList<struct btVendorProduct> btDCs;
#endif
signals:
void cloudUserNameChanged();
void cloudPasswordChanged();