subsurface/core/downloadfromdcthread.cpp
Dirk Hohndel 345e063eb5 Rewrite the matching code for BT devices
This should be much more robust in getting us the correct Bluetooth address
and the correct vendor / product for our selection.

When we pick a paired device, we extract the address right from its name.

Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2017-07-03 21:58:26 -07:00

392 lines
11 KiB
C++

#include "downloadfromdcthread.h"
#include "core/libdivecomputer.h"
#include <QDebug>
#include <QRegularExpression>
QStringList vendorList;
QHash<QString, QStringList> productList;
static QHash<QString, QStringList> mobileProductList; // BT, BLE or FTDI supported DCs for mobile
QMap<QString, dc_descriptor_t *> descriptorLookup;
static QString str_error(const char *fmt, ...)
{
va_list args;
va_start(args, fmt);
const QString str = QString().vsprintf(fmt, args);
va_end(args);
return str;
}
DownloadThread::DownloadThread()
{
m_data = DCDeviceData::instance();
}
void DownloadThread::run()
{
auto internalData = m_data->internalData();
internalData->descriptor = descriptorLookup[m_data->vendor() + m_data->product()];
internalData->download_table = &downloadTable;
#if defined(Q_OS_ANDROID)
// on Android we either use BT or we download via FTDI cable
if (!internalData->bluetooth_mode)
internalData->devname = "ftdi";
#endif
qDebug() << "Starting download from " << (internalData->bluetooth_mode ? "BT" : internalData->devname);
downloadTable.nr = 0;
qDebug() << "Starting the thread" << downloadTable.nr;
dive_table.preexisting = dive_table.nr;
Q_ASSERT(internalData->download_table != nullptr);
const char *errorText;
import_thread_cancelled = false;
if (!strcmp(internalData->vendor, "Uemis"))
errorText = do_uemis_import(internalData);
else
errorText = do_libdivecomputer_import(internalData);
if (errorText)
error = str_error(errorText, internalData->devname, internalData->vendor, internalData->product);
qDebug() << "Finishing the thread" << errorText << "dives downloaded" << downloadTable.nr;
}
static void fill_supported_mobile_list()
{
/* currently no BLE devices added as BLE backend is not ready yet */
#if defined(Q_OS_ANDROID)
/* BT, BLE and FTDI devices */
mobileProductList["Heinrichs Weikamp"] =
QStringList({{"OSTC Sport"}, {"OSTC 2N"}, {"OSTC 3"},
{"OSTC 3+"}, {"OSTC 4"}});
mobileProductList["Shearwater"] =
QStringList({{"Petrel"}, {"Petrel 2"}, {"Perdix"}});
mobileProductList["Suunto"] =
QStringList({"EON Steel"});
mobileProductList["Scubapro"] =
QStringList({{"G2"}});
#endif
#if defined(Q_OS_IOS)
/* BLE only, Qt does not support classic BT on iOS */
mobileProductList["Heinrichs Weikamp"] =
QStringList({{"OSTC 4"}});
mobileProductList["Shearwater"] =
QStringList({{"Petrel"}, {"Petrel 2"}, {"Perdix"}});
mobileProductList["Suunto"] =
QStringList({"EON Steel"});
mobileProductList["Scubapro"] =
QStringList({{"G2"}});
#endif
}
void fill_computer_list()
{
dc_iterator_t *iterator = NULL;
dc_descriptor_t *descriptor = NULL;
struct mydescriptor *mydescriptor;
fill_supported_mobile_list();
dc_descriptor_iterator(&iterator);
while (dc_iterator_next(iterator, &descriptor) == DC_STATUS_SUCCESS) {
const char *vendor = dc_descriptor_get_vendor(descriptor);
const char *product = dc_descriptor_get_product(descriptor);
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
if (!mobileProductList.contains(vendor))
continue;
#endif
if (!vendorList.contains(vendor))
vendorList.append(vendor);
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
if (!mobileProductList[vendor].contains(product))
continue;
#endif
if (!productList[vendor].contains(product))
productList[vendor].push_back(product);
descriptorLookup[QString(vendor) + QString(product)] = descriptor;
}
dc_iterator_free(iterator);
Q_FOREACH (QString vendor, vendorList)
qSort(productList[vendor]);
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
/* currently suppress the Uemis Zurich on Q_OS_ANDROID and Q_OS_IOS,
* as it is no BT device */
/* and add the Uemis Zurich which we are handling internally
THIS IS A HACK as we magically have a data structure here that
happens to match a data structure that is internal to libdivecomputer;
this WILL BREAK if libdivecomputer changes the dc_descriptor struct...
eventually the UEMIS code needs to move into libdivecomputer, I guess */
mydescriptor = (struct mydescriptor *)malloc(sizeof(struct mydescriptor));
mydescriptor->vendor = "Uemis";
mydescriptor->product = "Zurich";
mydescriptor->type = DC_FAMILY_NULL;
mydescriptor->model = 0;
if (!vendorList.contains("Uemis"))
vendorList.append("Uemis");
if (!productList["Uemis"].contains("Zurich"))
productList["Uemis"].push_back("Zurich");
descriptorLookup["UemisZurich"] = (dc_descriptor_t *)mydescriptor;
#endif
qSort(vendorList);
#if defined(SUBSURFACE_MOBILE) && defined(BT_SUPPORT)
vendorList.append(QObject::tr("Paired Bluetooth Devices"));
#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;
data.diveid = 0;
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;
}
QString DCDeviceData::vendor() const
{
return data.vendor;
}
QString DCDeviceData::product() const
{
return data.product;
}
QString DCDeviceData::devName() const
{
return data.devname;
}
QString DCDeviceData::descriptor() const
{
return "";
}
bool DCDeviceData::bluetoothMode() const
{
return data.bluetooth_mode;
}
bool DCDeviceData::forceDownload() const
{
return data.force_download;
}
bool DCDeviceData::createNewTrip() const
{
return data.create_new_trip;
}
int DCDeviceData::deviceId() const
{
return data.deviceid;
}
int DCDeviceData::diveId() const
{
return data.diveid;
}
void DCDeviceData::setVendor(const QString& vendor)
{
data.vendor = strdup(qPrintable(vendor));
}
void DCDeviceData::setProduct(const QString& product)
{
data.product = strdup(qPrintable(product));
}
void DCDeviceData::setDevName(const QString& devName)
{
data.devname = strdup(qPrintable(devName));
}
void DCDeviceData::setBluetoothMode(bool mode)
{
data.bluetooth_mode = mode;
}
void DCDeviceData::setForceDownload(bool force)
{
data.force_download = force;
}
void DCDeviceData::setCreateNewTrip(bool create)
{
data.create_new_trip = create;
}
void DCDeviceData::setDeviceId(int deviceId)
{
data.deviceid = deviceId;
}
void DCDeviceData::setDiveId(int diveId)
{
data.diveid = diveId;
}
void DCDeviceData::setSaveDump(bool save)
{
data.libdc_dump = save;
}
bool DCDeviceData::saveDump() const
{
return data.libdc_dump;
}
void DCDeviceData::setSaveLog(bool saveLog)
{
data.libdc_log = saveLog;
}
bool DCDeviceData::saveLog() const
{
return data.libdc_log;
}
device_data_t* DCDeviceData::internalData()
{
return &data;
}
int DCDeviceData::getDetectedVendorIndex(const QString &currentText)
{
#if defined(BT_SUPPORT)
QList<BTDiscovery::btVendorProduct> btDCs = BTDiscovery::instance()->getBtDcs();
// Pick the vendor of the first confirmed find of a DC (if any), but
// only return a true vendor, and not our virtual one
if (!btDCs.isEmpty() && currentText != QObject::tr("Paired Bluetooth Devices")) {
qDebug() << "getDetectedVendorIndex" << currentText << btDCs.first().vendorIdx;
return btDCs.first().vendorIdx;
}
#endif
return -1;
}
int DCDeviceData::getDetectedProductIndex(const QString &currentVendorText,
const QString &currentProductText)
{
#if defined(BT_SUPPORT)
QList<BTDiscovery::btVendorProduct> btDCs = BTDiscovery::instance()->getBtDcs();
// Display in the QML UI, the first found dive computer that is been
// detected as a possible real dive computer (and not some other paired
// BT device
if (currentVendorText != QObject::tr("Paired Bluetooth Devices") && !btDCs.isEmpty()) {
qDebug() << "getDetectedProductIndex" << btDCs.first().productIdx;
return btDCs.first().productIdx;
}
// if the above fails, display the selected paired device
if (currentVendorText == QObject::tr("Paired Bluetooth Devices")) {
qDebug() << "getDetectedProductIndex" << productList[currentVendorText].indexOf(currentProductText);
return productList[currentVendorText].indexOf(currentProductText);
}
#endif
return -1;
}
QString DCDeviceData::getDetectedDeviceAddress(const QString &currentVendorText,
const QString &currentProductText)
{
#if defined(BT_SUPPORT)
if (currentVendorText == QObject::tr("Paired Bluetooth Devices")) {
// simply get the address from the product text
QRegularExpression extractAddr(".*\\(([0-9A-FL:]*)\\)");
QRegularExpressionMatch m = extractAddr.match(currentProductText);
if (m.hasMatch()) {
qDebug() << "matched" << m.captured(1);
return m.captured(1);
}
}
// Otherwise, pull the vendor from the found devices that are possible real dive computers
// HACK: this assumes that dive computer names are unique across vendors
// and will only give you the first of multiple identically named dive computers - use
// the Paired Bluetooth Devices vendor in cases like that
QList<BTDiscovery::btVendorProduct> btDCs = BTDiscovery::instance()->getBtDcs();
BTDiscovery::btVendorProduct btDC;
Q_FOREACH(btDC, btDCs) {
if (currentProductText.startsWith(dc_descriptor_get_product(btDC.dcDescriptor)))
return btDC.btpdi.address;
}
#endif
return QStringLiteral("cannot determine address of dive computer");
}
QString DCDeviceData::getDeviceDescriptorVendor(const QString &currentVendorText,
const QString &currentProductText)
{
#if defined(BT_SUPPORT)
if (currentVendorText != QObject::tr("Paired Bluetooth Devices"))
return currentVendorText;
QList<BTDiscovery::btVendorProduct> btDCs = BTDiscovery::instance()->getBtDcs();
// Pull the vendor from the found devices that are possible real dive computers
// HACK: this assumes that dive computer names are unique across vendors
BTDiscovery::btVendorProduct btDC;
Q_FOREACH(btDC, btDCs) {
if (currentProductText.startsWith(dc_descriptor_get_product(btDC.dcDescriptor)))
return dc_descriptor_get_vendor(btDC.dcDescriptor);
}
#endif
return QStringLiteral("failed to detect vendor");
}
QString DCDeviceData::getDeviceDescriptorProduct(const QString &currentVendorText,
const QString &currentProductText)
{
#if defined(BT_SUPPORT)
if (currentVendorText != QObject::tr("Paired Bluetooth Devices"))
return currentProductText;
QList<BTDiscovery::btVendorProduct> btDCs = BTDiscovery::instance()->getBtDcs();
// Pull the canonical product from the found devices that are possible real dive computers
// HACK: this assumes that dive computer names are unique across vendors
BTDiscovery::btVendorProduct btDC;
Q_FOREACH(btDC, btDCs) {
if (currentProductText.startsWith(dc_descriptor_get_product(btDC.dcDescriptor)))
return dc_descriptor_get_product(btDC.dcDescriptor);
}
#endif
return QStringLiteral("failed to detect product");
}