Hi there, I attached you some patches which add the QtBluetooth serial implementation. The Bluetooth download mode is accessible from the DownloadFromDiveComputer widget.
In order to connect to a Bluetooth device I decided to remove the SDP discovery because it doesn't work for all devices. For the moment I try to connect to the device using the RFCOMM channel 1 because this is the default channel of the SPP service for most devices. If this doesn't work I try again on RFCOMM channel number 5 (for Petrel2 devices). The last patch is not mandatory. It adds some extra logs which can be useful for debugging. Claudiu
From 031f1ffaff034b725253994bf0fe5b4c3c296c08 Mon Sep 17 00:00:00 2001 From: Claudiu Olteanu <[email protected]> Date: Mon, 6 Jul 2015 16:18:06 +0300 Subject: [PATCH 1/4] Add a checkbox and a button for Bluetooth download mode The checkbox will be used to enable the Bluetooth downloading mode. The button will be used to create a dialog selection where the user will be able to scan and select remote devices. Signed-off-by: Claudiu Olteanu <[email protected]> --- qt-ui/downloadfromdivecomputer.cpp | 19 +++++++++++++++++++ qt-ui/downloadfromdivecomputer.h | 2 ++ qt-ui/downloadfromdivecomputer.ui | 17 +++++++++++++++++ 3 files changed, 38 insertions(+) diff --git a/qt-ui/downloadfromdivecomputer.cpp b/qt-ui/downloadfromdivecomputer.cpp index c5d57e6..f1bb058 100644 --- a/qt-ui/downloadfromdivecomputer.cpp +++ b/qt-ui/downloadfromdivecomputer.cpp @@ -99,6 +99,9 @@ DownloadFromDCWidget::DownloadFromDCWidget(QWidget *parent, Qt::WindowFlags f) : ui.ok->setEnabled(false); ui.downloadCancelRetryButton->setEnabled(true); ui.downloadCancelRetryButton->setText(tr("Download")); + ui.chooseBluetoothDevice->setEnabled(ui.bluetoothMode->isChecked()); + connect(ui.bluetoothMode, SIGNAL(stateChanged(int)), this, SLOT(enableBluetoothMode(int))); + connect(ui.chooseBluetoothDevice, SIGNAL(clicked()), this, SLOT(selectRemoteBluetoothDevice())); } void DownloadFromDCWidget::updateProgressBar() @@ -493,6 +496,8 @@ void DownloadFromDCWidget::markChildrenAsDisabled() ui.chooseDumpFile->setEnabled(false); ui.selectAllButton->setEnabled(false); ui.unselectAllButton->setEnabled(false); + ui.bluetoothMode->setEnabled(false); + ui.chooseBluetoothDevice->setEnabled(false); } void DownloadFromDCWidget::markChildrenAsEnabled() @@ -512,6 +517,20 @@ void DownloadFromDCWidget::markChildrenAsEnabled() ui.chooseDumpFile->setEnabled(true); ui.selectAllButton->setEnabled(true); ui.unselectAllButton->setEnabled(true); + ui.bluetoothMode->setEnabled(true); + ui.chooseBluetoothDevice->setEnabled(true); +} + +void DownloadFromDCWidget::selectRemoteBluetoothDevice() +{ + //TODO add implementation +} + +void DownloadFromDCWidget::enableBluetoothMode(int state) +{ + ui.chooseBluetoothDevice->setEnabled(state == Qt::Checked); + if (state == Qt::Checked) + selectRemoteBluetoothDevice(); } static void fillDeviceList(const char *name, void *data) diff --git a/qt-ui/downloadfromdivecomputer.h b/qt-ui/downloadfromdivecomputer.h index 92db09d..0b63d28 100644 --- a/qt-ui/downloadfromdivecomputer.h +++ b/qt-ui/downloadfromdivecomputer.h @@ -77,8 +77,10 @@ slots: void updateProgressBar(); void checkLogFile(int state); void checkDumpFile(int state); + void enableBluetoothMode(int state); void pickDumpFile(); void pickLogFile(); + void selectRemoteBluetoothDevice(); private: void markChildrenAsDisabled(); diff --git a/qt-ui/downloadfromdivecomputer.ui b/qt-ui/downloadfromdivecomputer.ui index ff80935..f232967 100644 --- a/qt-ui/downloadfromdivecomputer.ui +++ b/qt-ui/downloadfromdivecomputer.ui @@ -116,6 +116,23 @@ </property> </widget> </item> + <item row="11" column="0"> + <widget class="QCheckBox" name="bluetoothMode"> + <property name="text"> + <string>Choose Bluetooth Download mode</string> + </property> + </widget> + </item> + <item row="11" column="1"> + <widget class="QToolButton" name="chooseBluetoothDevice"> + <property name="toolTip"> + <string>Select a remote Bluetooth device.</string> + </property> + <property name="text"> + <string>...</string> + </property> + </widget> + </item> <item row="0" column="0" colspan="2"> <widget class="QLabel" name="label"> <property name="text"> -- 2.1.4
From 87b3607918d1c21be650c7cad1ba4c740bd537ef Mon Sep 17 00:00:00 2001 From: Claudiu Olteanu <[email protected]> Date: Mon, 6 Jul 2015 16:35:13 +0300 Subject: [PATCH 2/4] Add a dialog for remote Bluetooth devices selection Implement a dialog which can be used for remote Bluetooth devices selection and to control the local Bluetooth device. Functionalities of the widget: - expose information about the local BT device - scan for remote BT devices - pair/unpair with a remote BT device - turn on/off the local BT device - logging - save the selected BT device The selection dialog is created when the bluetoothMode checkbox is enabled. Signed-off-by: Claudiu Olteanu <[email protected]> --- CMakeLists.txt | 5 +- libdivecomputer.h | 1 + qt-ui/btdeviceselectiondialog.cpp | 261 +++++++++++++++++++++++++++++++++++++ qt-ui/btdeviceselectiondialog.h | 46 +++++++ qt-ui/btdeviceselectiondialog.ui | 214 ++++++++++++++++++++++++++++++ qt-ui/downloadfromdivecomputer.cpp | 27 +++- qt-ui/downloadfromdivecomputer.h | 3 + 7 files changed, 553 insertions(+), 4 deletions(-) create mode 100644 qt-ui/btdeviceselectiondialog.cpp create mode 100644 qt-ui/btdeviceselectiondialog.h create mode 100644 qt-ui/btdeviceselectiondialog.ui diff --git a/CMakeLists.txt b/CMakeLists.txt index dce8440..eac9853 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -161,8 +161,8 @@ if(CMAKE_SYSTEM_NAME STREQUAL "Android") set(ANDROID_PKG AndroidExtras) set(ANDROID_LIB Qt5::AndroidExtras) endif() -find_package(Qt5 REQUIRED COMPONENTS Core Concurrent Widgets Network ${WEBKIT_PKG} ${PRINTING_PKG} Svg Test LinguistTools ${QT_QUICK_PKG} ${ANDROID_PKG}) -set(QT_LIBRARIES Qt5::Core Qt5::Concurrent Qt5::Widgets Qt5::Network ${WEBKIT_LIB} ${PRINTING_LIB} Qt5::Svg ${QT_QUICK_LIB} ${ANDROID_LIB}) +find_package(Qt5 REQUIRED COMPONENTS Core Concurrent Widgets Network ${WEBKIT_PKG} ${PRINTING_PKG} Svg Test LinguistTools ${QT_QUICK_PKG} ${ANDROID_PKG} Bluetooth) +set(QT_LIBRARIES Qt5::Core Qt5::Concurrent Qt5::Widgets Qt5::Network ${WEBKIT_LIB} ${PRINTING_LIB} Qt5::Svg ${QT_QUICK_LIB} ${ANDROID_LIB} Qt5::Bluetooth) set(QT_TEST_LIBRARIES ${QT_LIBRARIES} Qt5::Test) # Generate the ssrf-config.h every 'make' @@ -352,6 +352,7 @@ source_group("Subsurface Models" FILES ${SUBSURFACE_MODELS}) set(SUBSURFACE_INTERFACE qt-ui/updatemanager.cpp qt-ui/about.cpp + qt-ui/btdeviceselectiondialog.cpp qt-ui/divecomputermanagementdialog.cpp qt-ui/divelistview.cpp qt-ui/diveplanner.cpp diff --git a/libdivecomputer.h b/libdivecomputer.h index dfb6267..f5c0cad 100644 --- a/libdivecomputer.h +++ b/libdivecomputer.h @@ -37,6 +37,7 @@ typedef struct device_data_t bool create_new_trip; bool libdc_log; bool libdc_dump; + bool bluetooth_mode; FILE *libdc_logfile; struct dive_table *download_table; } device_data_t; diff --git a/qt-ui/btdeviceselectiondialog.cpp b/qt-ui/btdeviceselectiondialog.cpp new file mode 100644 index 0000000..436dca5 --- /dev/null +++ b/qt-ui/btdeviceselectiondialog.cpp @@ -0,0 +1,261 @@ +#include <QShortcut> +#include <QDebug> +#include <QMessageBox> +#include <QMenu> + +#include "ui_btdeviceselectiondialog.h" +#include "btdeviceselectiondialog.h" + +BtDeviceSelectionDialog::BtDeviceSelectionDialog(QWidget *parent) : + QDialog(parent), + localDevice(new QBluetoothLocalDevice), + ui(new Ui::BtDeviceSelectionDialog) +{ + // Check if Bluetooth is available on this device + if (!localDevice->isValid()) { + QMessageBox::warning(this, tr("Warning"), + "This should never happen, please contact the Subsurface developers " + "and tell them that the Bluetooth download mode doesn't work."); + return; + } + + ui->setupUi(this); + + // Quit button callbacks + QShortcut *quit = new QShortcut(QKeySequence(Qt::CTRL + Qt::Key_Q), this); + connect(quit, SIGNAL(activated()), this, SLOT(reject())); + connect(ui->quit, SIGNAL(clicked()), this, SLOT(reject())); + + // Disable the save button because there is no device selected + ui->save->setEnabled(false); + + connect(ui->discoveredDevicesList, SIGNAL(itemActivated(QListWidgetItem*)), + this, SLOT(itemActivated(QListWidgetItem*))); + + // Set UI information about the local device + ui->deviceAddress->setText(localDevice->address().toString()); + ui->deviceName->setText(localDevice->name()); + + connect(localDevice, SIGNAL(hostModeStateChanged(QBluetoothLocalDevice::HostMode)), + this, SLOT(hostModeStateChanged(QBluetoothLocalDevice::HostMode))); + + // Initialize the state of the local device and activate/deactive the scan button + hostModeStateChanged(localDevice->hostMode()); + + // Intialize the discovery agent + remoteDeviceDiscoveryAgent = new QBluetoothDeviceDiscoveryAgent(); + + connect(remoteDeviceDiscoveryAgent, SIGNAL(deviceDiscovered(QBluetoothDeviceInfo)), + this, SLOT(addRemoteDevice(QBluetoothDeviceInfo))); + connect(remoteDeviceDiscoveryAgent, SIGNAL(finished()), + this, SLOT(remoteDeviceScanFinished())); + + // Add context menu for devices to be able to pair them + ui->discoveredDevicesList->setContextMenuPolicy(Qt::CustomContextMenu); + connect(ui->discoveredDevicesList, SIGNAL(customContextMenuRequested(QPoint)), + this, SLOT(displayPairingMenu(QPoint))); + connect(localDevice, SIGNAL(pairingFinished(QBluetoothAddress, QBluetoothLocalDevice::Pairing)), + this, SLOT(pairingFinished(QBluetoothAddress, QBluetoothLocalDevice::Pairing))); + + connect(localDevice, SIGNAL(error(QBluetoothLocalDevice::Error)), + this, SLOT(error(QBluetoothLocalDevice::Error))); +} + +BtDeviceSelectionDialog::~BtDeviceSelectionDialog() +{ + delete ui; +} + +void BtDeviceSelectionDialog::on_changeDeviceState_clicked() +{ + if (localDevice->hostMode() == QBluetoothLocalDevice::HostPoweredOff) { + ui->dialogStatus->setText("Trying to turn on the local Bluetooth device..."); + localDevice->powerOn(); + } else { + ui->dialogStatus->setText("Trying to turn off the local Bluetooth device..."); + localDevice->setHostMode(QBluetoothLocalDevice::HostPoweredOff); + } +} + +void BtDeviceSelectionDialog::on_save_clicked() +{ + // Get the selected device. There will be always a selected device if the save button is enabled. + QListWidgetItem *currentItem = ui->discoveredDevicesList->currentItem(); + QBluetoothDeviceInfo remoteDeviceInfo = currentItem->data(Qt::UserRole).value<QBluetoothDeviceInfo>(); + + // Save the selected device + selectedRemoteDeviceInfo = QSharedPointer<QBluetoothDeviceInfo>(new QBluetoothDeviceInfo(remoteDeviceInfo)); + + // Close the device selection dialog and set the result code to Accepted + accept(); +} + +void BtDeviceSelectionDialog::on_clear_clicked() +{ + ui->dialogStatus->setText("Remote devices list was cleaned."); + ui->discoveredDevicesList->clear(); + ui->save->setEnabled(false); +} + +void BtDeviceSelectionDialog::on_scan_clicked() +{ + ui->dialogStatus->setText("Scanning for remote devices..."); + remoteDeviceDiscoveryAgent->start(); + ui->scan->setEnabled(false); +} + +void BtDeviceSelectionDialog::remoteDeviceScanFinished() +{ + ui->dialogStatus->setText("Scanning finished."); + ui->scan->setEnabled(true); +} + +void BtDeviceSelectionDialog::hostModeStateChanged(QBluetoothLocalDevice::HostMode mode) +{ + bool on = !(mode == QBluetoothLocalDevice::HostPoweredOff); + + ui->dialogStatus->setText(QString("The local Bluetooth device was turned %1.") + .arg(on? "ON" : "OFF")); + ui->deviceState->setChecked(on); + ui->scan->setEnabled(on); +} + +void BtDeviceSelectionDialog::addRemoteDevice(const QBluetoothDeviceInfo &remoteDeviceInfo) +{ + QString deviceLable = QString("%1 (%2)").arg(remoteDeviceInfo.name()).arg(remoteDeviceInfo.address().toString()); + QList<QListWidgetItem *> itemsWithSameSignature = ui->discoveredDevicesList->findItems(deviceLable, Qt::MatchStartsWith); + + // Check if the remote device is already in the list + if (itemsWithSameSignature.empty()) { + QListWidgetItem *item = new QListWidgetItem(deviceLable); + QBluetoothLocalDevice::Pairing pairingStatus = localDevice->pairingStatus(remoteDeviceInfo.address()); + item->setData(Qt::UserRole, QVariant::fromValue(remoteDeviceInfo)); + + if (pairingStatus == QBluetoothLocalDevice::Paired) { + item->setText(QString("%1 [State: PAIRED]").arg(item->text())); + item->setBackgroundColor(QColor(Qt::gray)); + } else if (pairingStatus == QBluetoothLocalDevice::AuthorizedPaired) { + item->setText(QString("%1 [State: AUTHORIZED_PAIRED]").arg(item->text())); + item->setBackgroundColor(QColor(Qt::blue)); + } else { + item->setText(QString("%1 [State: UNPAIRED]").arg(item->text())); + item->setTextColor(QColor(Qt::black)); + } + + ui->discoveredDevicesList->addItem(item); + } +} + +void BtDeviceSelectionDialog::itemActivated(QListWidgetItem *item) +{ + QBluetoothDeviceInfo remoteDeviceInfo = item->data(Qt::UserRole).value<QBluetoothDeviceInfo>(); + QBluetoothLocalDevice::Pairing pairingStatus = localDevice->pairingStatus(remoteDeviceInfo.address()); + + if (pairingStatus == QBluetoothLocalDevice::Unpaired) { + ui->dialogStatus->setText(QString("The device %1 must be paired in order to be used. Please use the context menu for pairing options.") + .arg(remoteDeviceInfo.address().toString())); + ui->save->setEnabled(false); + } else { + ui->dialogStatus->setText(QString("The device %1 can be used for connection. You can press the Save button.") + .arg(remoteDeviceInfo.address().toString())); + ui->save->setEnabled(true); + } +} + +void BtDeviceSelectionDialog::displayPairingMenu(const QPoint &pos) +{ + QMenu menu(this); + QAction *pairAction = menu.addAction("Pair"); + QAction *removePairAction = menu.addAction("Remove Pairing"); + QAction *chosenAction = menu.exec(ui->discoveredDevicesList->viewport()->mapToGlobal(pos)); + QListWidgetItem *currentItem = ui->discoveredDevicesList->currentItem(); + QBluetoothDeviceInfo currentRemoteDeviceInfo = currentItem->data(Qt::UserRole).value<QBluetoothDeviceInfo>(); + QBluetoothLocalDevice::Pairing pairingStatus = localDevice->pairingStatus(currentRemoteDeviceInfo.address()); + + //TODO: disable the actions + if (pairingStatus == QBluetoothLocalDevice::Unpaired) { + pairAction->setEnabled(true); + removePairAction->setEnabled(false); + } else { + pairAction->setEnabled(false); + removePairAction->setEnabled(true); + } + + if (chosenAction == pairAction) { + ui->dialogStatus->setText(QString("Trying to pair device %1") + .arg(currentRemoteDeviceInfo.address().toString())); + localDevice->requestPairing(currentRemoteDeviceInfo.address(), QBluetoothLocalDevice::Paired); + } else if (chosenAction == removePairAction) { + ui->dialogStatus->setText(QString("Trying to unpair device %1") + .arg(currentRemoteDeviceInfo.address().toString())); + localDevice->requestPairing(currentRemoteDeviceInfo.address(), QBluetoothLocalDevice::Unpaired); + } +} + +void BtDeviceSelectionDialog::pairingFinished(const QBluetoothAddress &address, QBluetoothLocalDevice::Pairing pairing) +{ + QString remoteDeviceStringAddress = address.toString(); + QList<QListWidgetItem *> items = ui->discoveredDevicesList->findItems(remoteDeviceStringAddress, Qt::MatchContains); + + if (pairing == QBluetoothLocalDevice::Paired || pairing == QBluetoothLocalDevice::Paired ) { + ui->dialogStatus->setText(QString("Device %1 was paired.") + .arg(remoteDeviceStringAddress)); + + for (int i = 0; i < items.count(); ++i) { + QListWidgetItem *item = items.at(i); + + item->setText(QString("%1 [State: PAIRED]").arg(remoteDeviceStringAddress)); + item->setBackgroundColor(QColor(Qt::gray)); + } + + QListWidgetItem *currentItem = ui->discoveredDevicesList->currentItem(); + + if (currentItem != NULL && currentItem->text().contains(remoteDeviceStringAddress, Qt::CaseInsensitive)) { + ui->dialogStatus->setText(QString("The device %1 can now be used for connection. You can press the Save button.") + .arg(remoteDeviceStringAddress)); + ui->save->setEnabled(true); + } + } else { + ui->dialogStatus->setText(QString("Device %1 was unpaired.") + .arg(remoteDeviceStringAddress)); + + for (int i = 0; i < items.count(); ++i) { + QListWidgetItem *item = items.at(i); + + item->setText(QString("%1 [State: UNPAIRED]").arg(remoteDeviceStringAddress)); + item->setBackgroundColor(QColor(Qt::white)); + } + + QListWidgetItem *currentItem = ui->discoveredDevicesList->currentItem(); + + if (currentItem != NULL && currentItem->text().contains(remoteDeviceStringAddress, Qt::CaseInsensitive)) { + ui->dialogStatus->setText(QString("The device %1 must be paired in order to be used. Please use the context menu for pairing options.") + .arg(remoteDeviceStringAddress)); + ui->save->setEnabled(false); + } + } +} + +void BtDeviceSelectionDialog::error(QBluetoothLocalDevice::Error error) +{ + ui->dialogStatus->setText(QString("Local device error: %1.") + .arg((error == QBluetoothLocalDevice::PairingError)? "Pairing error" : "Unknown error")); +} + +QString BtDeviceSelectionDialog::getSelectedDeviceAddress() +{ + if (selectedRemoteDeviceInfo) { + return selectedRemoteDeviceInfo.data()->address().toString(); + } + + return QString(); +} + +QString BtDeviceSelectionDialog::getSelectedDeviceName() +{ + if (selectedRemoteDeviceInfo) { + return selectedRemoteDeviceInfo.data()->name(); + } + + return QString(); +} diff --git a/qt-ui/btdeviceselectiondialog.h b/qt-ui/btdeviceselectiondialog.h new file mode 100644 index 0000000..6bcc43f --- /dev/null +++ b/qt-ui/btdeviceselectiondialog.h @@ -0,0 +1,46 @@ +#ifndef BTDEVICESELECTIONDIALOG_H +#define BTDEVICESELECTIONDIALOG_H + +#include <QDialog> +#include <QListWidgetItem> +#include <QPointer> +#include <QtBluetooth/QBluetoothLocalDevice> +#include <QtBluetooth/qbluetoothglobal.h> +#include <QtBluetooth/QBluetoothDeviceDiscoveryAgent> + +Q_DECLARE_METATYPE(QBluetoothDeviceInfo) + +namespace Ui { + class BtDeviceSelectionDialog; +} + +class BtDeviceSelectionDialog : public QDialog { + Q_OBJECT + +public: + explicit BtDeviceSelectionDialog(QWidget *parent = 0); + ~BtDeviceSelectionDialog(); + QString getSelectedDeviceAddress(); + QString getSelectedDeviceName(); + +private slots: + void on_changeDeviceState_clicked(); + void on_save_clicked(); + void on_clear_clicked(); + void on_scan_clicked(); + void remoteDeviceScanFinished(); + void hostModeStateChanged(QBluetoothLocalDevice::HostMode mode); + void addRemoteDevice(const QBluetoothDeviceInfo &remoteDeviceInfo); + void itemActivated(QListWidgetItem *item); + void displayPairingMenu(const QPoint &pos); + void pairingFinished(const QBluetoothAddress &address,QBluetoothLocalDevice::Pairing pairing); + void error(QBluetoothLocalDevice::Error error); + +private: + Ui::BtDeviceSelectionDialog *ui; + QBluetoothLocalDevice *localDevice; + QBluetoothDeviceDiscoveryAgent *remoteDeviceDiscoveryAgent; + QSharedPointer<QBluetoothDeviceInfo> selectedRemoteDeviceInfo; +}; + +#endif // BTDEVICESELECTIONDIALOG_H diff --git a/qt-ui/btdeviceselectiondialog.ui b/qt-ui/btdeviceselectiondialog.ui new file mode 100644 index 0000000..c28bdcb --- /dev/null +++ b/qt-ui/btdeviceselectiondialog.ui @@ -0,0 +1,214 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>BtDeviceSelectionDialog</class> + <widget class="QDialog" name="BtDeviceSelectionDialog"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>735</width> + <height>460</height> + </rect> + </property> + <property name="windowTitle"> + <string>Remote Bluetooth device selection</string> + </property> + <layout class="QGridLayout" name="gridLayout"> + <item row="0" column="0"> + <widget class="QLabel" name="discoveredDevicesLabel"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="font"> + <font> + <weight>75</weight> + <bold>true</bold> + </font> + </property> + <property name="text"> + <string>Discovered devices</string> + </property> + </widget> + </item> + <item row="3" column="1"> + <layout class="QHBoxLayout" name="dialogControls"> + <item> + <widget class="QPushButton" name="save"> + <property name="text"> + <string>Save</string> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="quit"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Minimum" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>Quit</string> + </property> + </widget> + </item> + </layout> + </item> + <item row="1" column="0" rowspan="2"> + <layout class="QVBoxLayout" name="remoteDevicesSection"> + <item> + <widget class="QListWidget" name="discoveredDevicesList"> + <property name="sizePolicy"> + <sizepolicy hsizetype="MinimumExpanding" vsizetype="Expanding"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + </widget> + </item> + <item> + <layout class="QVBoxLayout" name="scanningControls"> + <item> + <widget class="QPushButton" name="scan"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Minimum" vsizetype="Minimum"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>Scan</string> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="clear"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Minimum" vsizetype="Minimum"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>Clear</string> + </property> + </widget> + </item> + </layout> + </item> + </layout> + </item> + <item row="1" column="1"> + <widget class="QGroupBox" name="localDeviceDetails"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Minimum" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="font"> + <font> + <weight>75</weight> + <bold>true</bold> + </font> + </property> + <property name="title"> + <string>Local Bluetooth device details</string> + </property> + <property name="flat"> + <bool>false</bool> + </property> + <layout class="QFormLayout" name="formLayout_2"> + <item row="0" column="0"> + <widget class="QLabel" name="deviceNameLable"> + <property name="text"> + <string>Name: </string> + </property> + </widget> + </item> + <item row="0" column="1"> + <widget class="QLineEdit" name="deviceName"> + <property name="readOnly"> + <bool>true</bool> + </property> + </widget> + </item> + <item row="1" column="0"> + <widget class="QLabel" name="deviceAddressLable"> + <property name="text"> + <string>Address:</string> + </property> + </widget> + </item> + <item row="1" column="1"> + <widget class="QLineEdit" name="deviceAddress"> + <property name="readOnly"> + <bool>true</bool> + </property> + </widget> + </item> + <item row="2" column="1"> + <widget class="QCheckBox" name="deviceState"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="sizePolicy"> + <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="font"> + <font> + <weight>75</weight> + <bold>true</bold> + </font> + </property> + <property name="text"> + <string>Bluetooth powered on</string> + </property> + <property name="checkable"> + <bool>true</bool> + </property> + </widget> + </item> + <item row="3" column="1"> + <widget class="QPushButton" name="changeDeviceState"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Expanding" vsizetype="Minimum"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="font"> + <font> + <weight>50</weight> + <bold>false</bold> + </font> + </property> + <property name="text"> + <string>Turn On/Off</string> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item row="3" column="0"> + <widget class="QLabel" name="dialogStatus"> + <property name="text"> + <string/> + </property> + <property name="wordWrap"> + <bool>true</bool> + </property> + </widget> + </item> + </layout> + </widget> + <resources/> + <connections/> +</ui> diff --git a/qt-ui/downloadfromdivecomputer.cpp b/qt-ui/downloadfromdivecomputer.cpp index f1bb058..276e404 100644 --- a/qt-ui/downloadfromdivecomputer.cpp +++ b/qt-ui/downloadfromdivecomputer.cpp @@ -99,6 +99,8 @@ DownloadFromDCWidget::DownloadFromDCWidget(QWidget *parent, Qt::WindowFlags f) : ui.ok->setEnabled(false); ui.downloadCancelRetryButton->setEnabled(true); ui.downloadCancelRetryButton->setText(tr("Download")); + + btDeviceSelectionDialog = 0; ui.chooseBluetoothDevice->setEnabled(ui.bluetoothMode->isChecked()); connect(ui.bluetoothMode, SIGNAL(stateChanged(int)), this, SLOT(enableBluetoothMode(int))); connect(ui.chooseBluetoothDevice, SIGNAL(clicked()), this, SLOT(selectRemoteBluetoothDevice())); @@ -311,7 +313,11 @@ void DownloadFromDCWidget::on_downloadCancelRetryButton_clicked() data.vendor = strdup(ui.vendor->currentText().toUtf8().data()); data.product = strdup(ui.product->currentText().toUtf8().data()); - if (same_string(data.vendor, "Uemis")) { + data.bluetooth_mode = ui.bluetoothMode->isChecked(); + if (data.bluetooth_mode) { + // Get the selected device address + data.devname = strdup(btDeviceSelectionDialog->getSelectedDeviceAddress().toUtf8().data()); + } else if (same_string(data.vendor, "Uemis")) { char *colon; char *devname = strdup(ui.device->currentText().toUtf8().data()); @@ -523,7 +529,24 @@ void DownloadFromDCWidget::markChildrenAsEnabled() void DownloadFromDCWidget::selectRemoteBluetoothDevice() { - //TODO add implementation + if (!btDeviceSelectionDialog) { + btDeviceSelectionDialog = new BtDeviceSelectionDialog(this); + connect(btDeviceSelectionDialog, SIGNAL(finished(int)), + this, SLOT(bluetoothSelectionDialogIsFinished(int))); + } + + btDeviceSelectionDialog->show(); +} + +void DownloadFromDCWidget::bluetoothSelectionDialogIsFinished(int result) +{ + if (result == QDialog::Accepted) { + /* Make the selected Bluetooth device default */ + ui.device->setCurrentText(btDeviceSelectionDialog->getSelectedDeviceName()); + } else if (result == QDialog::Rejected){ + /* Disable Bluetooth download mode */ + ui.bluetoothMode->setChecked(false); + } } void DownloadFromDCWidget::enableBluetoothMode(int state) diff --git a/qt-ui/downloadfromdivecomputer.h b/qt-ui/downloadfromdivecomputer.h index 0b63d28..734e5f7 100644 --- a/qt-ui/downloadfromdivecomputer.h +++ b/qt-ui/downloadfromdivecomputer.h @@ -10,6 +10,7 @@ #include "libdivecomputer.h" #include "configuredivecomputerdialog.h" #include "ui_downloadfromdivecomputer.h" +#include "btdeviceselectiondialog.h" class QStringListModel; @@ -81,6 +82,7 @@ slots: void pickDumpFile(); void pickLogFile(); void selectRemoteBluetoothDevice(); + void bluetoothSelectionDialogIsFinished(int result); private: void markChildrenAsDisabled(); @@ -106,6 +108,7 @@ private: bool dumpWarningShown; OstcFirmwareCheck *ostcFirmwareCheck; DiveImportedModel *diveImportedModel; + BtDeviceSelectionDialog *btDeviceSelectionDialog; public: bool preferDownloaded(); -- 2.1.4
From c10e60686af35b374be739bf190f1b47a628be0a Mon Sep 17 00:00:00 2001 From: Claudiu Olteanu <[email protected]> Date: Mon, 6 Jul 2015 17:07:34 +0300 Subject: [PATCH 3/4] Implement the custom Bluetooth serial communication and use it Create a custom Bluetooth serial communication using the QTBluetooth API and use it when the Bluetooth download mode is enabled. First try to connect on RFCOMM channel 1 because this is the default RFCOMM channel of SPP service for most devices. If this doesn't work try again on RFCOMM channel number 5 because it could be a Petrel2 device. Add a fake open function for the custom implementation. This is used when the selected device is HW OSTC 2N and the Bluetooth mode is activated, then fake the open call of the serial device. Signed-off-by: Claudiu Olteanu <[email protected]> --- CMakeLists.txt | 1 + configuredivecomputerthreads.cpp | 10 ++ libdivecomputer.c | 22 +++- libdivecomputer.h | 1 + qtserialbluetooth.cpp | 240 +++++++++++++++++++++++++++++++++++++++ 5 files changed, 271 insertions(+), 3 deletions(-) create mode 100644 qtserialbluetooth.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index eac9853..77d2026 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -314,6 +314,7 @@ set(SUBSURFACE_CORE_LIB_SRCS windowtitleupdate.cpp divelogexportlogic.cpp qt-init.cpp + qtserialbluetooth.cpp ${PLATFORM_SRC} ) source_group("Subsurface Core" FILES ${SUBSURFACE_CORE_LIB_SRCS}) diff --git a/configuredivecomputerthreads.cpp b/configuredivecomputerthreads.cpp index 5c610db..e074660 100644 --- a/configuredivecomputerthreads.cpp +++ b/configuredivecomputerthreads.cpp @@ -82,6 +82,16 @@ static dc_status_t local_dc_device_open(dc_device_t **out, dc_context_t *context } #define dc_device_open local_dc_device_open +// Fake the custom open function +static dc_status_t local_dc_device_custom_open(dc_device_t **out, dc_context_t *context, dc_descriptor_t *descriptor, dc_serial_t *serial) +{ + if (strcmp(dc_descriptor_get_vendor(descriptor), "Heinrichs Weikamp") == 0 &&strcmp(dc_descriptor_get_product(descriptor), "OSTC 2N") == 0) + return DC_STATUS_SUCCESS; + else + return dc_device_custom_open(out, context, descriptor, serial); +} +#define dc_device_custom_open local_dc_device_custom_open + static dc_status_t local_hw_ostc_device_eeprom_read(void *ignored, unsigned char bank, unsigned char data[], unsigned int data_size) { FILE *f; diff --git a/libdivecomputer.c b/libdivecomputer.c index 24f4d0f..e308c13 100644 --- a/libdivecomputer.c +++ b/libdivecomputer.c @@ -923,14 +923,30 @@ const char *do_libdivecomputer_import(device_data_t *data) } err = translate("gettextFromC", "Unable to open %s %s (%s)"); - rc = dc_device_open(&data->device, data->context, data->descriptor, data->devname); + + if (data->bluetooth_mode) { + dc_serial_t *serial_device; + + rc = dc_serial_qt_open(&serial_device, data->context, data->devname); + if (rc == DC_STATUS_SUCCESS) { + rc = dc_device_custom_open(&data->device, data->context, data->descriptor, serial_device); + } else { + report_error(errmsg(rc)); + } + + } else { + rc = dc_device_open(&data->device, data->context, data->descriptor, data->devname); + + if (rc != DC_STATUS_SUCCESS && subsurface_access(data->devname, R_OK | W_OK) != 0) + err = translate("gettextFromC", "Insufficient privileges to open the device %s %s (%s)"); + } + if (rc == DC_STATUS_SUCCESS) { err = do_device_import(data); /* TODO: Show the logfile to the user on error. */ dc_device_close(data->device); data->device = NULL; - } else if (subsurface_access(data->devname, R_OK | W_OK) != 0) - err = translate("gettextFromC", "Insufficient privileges to open the device %s %s (%s)"); + } dc_context_free(data->context); data->context = NULL; diff --git a/libdivecomputer.h b/libdivecomputer.h index f5c0cad..649d898 100644 --- a/libdivecomputer.h +++ b/libdivecomputer.h @@ -53,6 +53,7 @@ extern const char *progress_bar_text; extern double progress_bar_fraction; extern char *logfile_name; extern char *dumpfile_name; +extern dc_status_t dc_serial_qt_open(dc_serial_t **out, dc_context_t *context, const char *devaddr); #ifdef __cplusplus } diff --git a/qtserialbluetooth.cpp b/qtserialbluetooth.cpp new file mode 100644 index 0000000..b718456 --- /dev/null +++ b/qtserialbluetooth.cpp @@ -0,0 +1,240 @@ +#include <errno.h> + +#include <QtBluetooth/QBluetoothAddress> +#include <QtBluetooth/QBluetoothSocket> +#include <QEventLoop> +#include <QTimer> + +#include <libdivecomputer/custom_serial.h> + +extern "C" { +typedef struct serial_t { + /* Library context. */ + dc_context_t *context; + /* + * RFCOMM socket used for Bluetooth Serial communication. + */ + QBluetoothSocket *socket; + long timeout; +} serial_t; + +static int qt_serial_open(serial_t **out, dc_context_t *context, const char* devaddr) +{ + if (out == NULL) + return DC_STATUS_INVALIDARGS; + + // Allocate memory. + serial_t *serial_port = (serial_t *) malloc (sizeof (serial_t)); + if (serial_port == NULL) { + return DC_STATUS_NOMEMORY; + } + + // Library context. + serial_port->context = context; + + // Default to blocking reads. + serial_port->timeout = -1; + + // Create a RFCOMM socket + serial_port->socket = new QBluetoothSocket(QBluetoothServiceInfo::RfcommProtocol); + + // Wait until the connection succeeds or until an error occurs + QEventLoop loop; + loop.connect(serial_port->socket, SIGNAL(connected()), SLOT(quit())); + loop.connect(serial_port->socket, SIGNAL(error(QBluetoothSocket::SocketError)), SLOT(quit())); + + // Create a timer. If the connection doesn't succeed after five seconds or no error occurs then stop the opening step + QTimer timer; + int msec = 5000; + timer.setSingleShot(true); + loop.connect(&timer, SIGNAL(timeout()), SLOT(quit())); + + // First try to connect on RFCOMM channel 1. This is the default channel for most devices + QBluetoothAddress remoteDeviceAddress(devaddr); + serial_port->socket->connectToService(remoteDeviceAddress, 1); + timer.start(msec); + loop.exec(); + + if (serial_port->socket->state() == QBluetoothSocket::ConnectingState) { + // It seems that the connection on channel 1 took more than expected. Wait another 15 seconds + timer.start(3 * msec); + loop.exec(); + } else if (serial_port->socket->state() == QBluetoothSocket::UnconnectedState) { + // Try to connect on channel number 5. Maybe this is a Shearwater Petrel2 device. + serial_port->socket->connectToService(remoteDeviceAddress, 5); + timer.start(msec); + loop.exec(); + + if (serial_port->socket->state() == QBluetoothSocket::ConnectingState) { + // It seems that the connection on channel 5 took more than expected. Wait another 15 seconds + timer.start(3 * msec); + loop.exec(); + } + } + + if (serial_port->socket->socketDescriptor() == -1 || serial_port->socket->state() != QBluetoothSocket::ConnectedState) { + free (serial_port); + + // Get the latest error and try to match it with one from libdivecomputer + QBluetoothSocket::SocketError err = serial_port->socket->error(); + switch(err) { + case QBluetoothSocket::HostNotFoundError: + case QBluetoothSocket::ServiceNotFoundError: + return DC_STATUS_NODEVICE; + case QBluetoothSocket::UnsupportedProtocolError: + return DC_STATUS_PROTOCOL; + case QBluetoothSocket::OperationError: + return DC_STATUS_UNSUPPORTED; + case QBluetoothSocket::NetworkError: + return DC_STATUS_IO; + default: + return QBluetoothSocket::UnknownSocketError; + } + } + + *out = serial_port; + + return DC_STATUS_SUCCESS; +} + +static int qt_serial_close(serial_t *device) +{ + if (device == NULL || device->socket == NULL) + return DC_STATUS_SUCCESS; + + device->socket->close(); + + delete device->socket; + free(device); + + return DC_STATUS_SUCCESS; +} + +static int qt_serial_read(serial_t *device, void* data, unsigned int size) +{ + if (device == NULL || device->socket == NULL) + return DC_STATUS_INVALIDARGS; + + unsigned int nbytes = 0, rc; + + while(nbytes < size) + { + device->socket->waitForReadyRead(device->timeout); + + rc = device->socket->read((char *) data + nbytes, size - nbytes); + + if (rc < 0) { + if (errno == EINTR || errno == EAGAIN) + continue; // Retry. + + return -1; // Something really bad happened :-( + } else if (rc == 0) { + // Wait until the device is available for read operations + QEventLoop loop; + loop.connect(device->socket, SIGNAL(readyRead()), SLOT(quit())); + loop.exec(); + } + + nbytes += rc; + } + + return nbytes; +} + +static int qt_serial_write(serial_t *device, const void* data, unsigned int size) +{ + if (device == NULL || device->socket == NULL) + return DC_STATUS_INVALIDARGS; + + unsigned int nbytes = 0, rc; + + while(nbytes < size) + { + device->socket->waitForBytesWritten(device->timeout); + + rc = device->socket->write((char *) data + nbytes, size - nbytes); + + if (rc < 0) { + if (errno == EINTR || errno == EAGAIN) + continue; // Retry. + + return -1; // Something really bad happened :-( + } else if (rc == 0) { + break; + } + + nbytes += rc; + } + + return nbytes; +} + +static int qt_serial_flush(serial_t *device, int queue) +{ + if (device == NULL || device->socket == NULL) + return DC_STATUS_INVALIDARGS; + + //TODO: add implementation + + return DC_STATUS_SUCCESS; +} + +static int qt_serial_get_received(serial_t *device) +{ + if (device == NULL || device->socket == NULL) + return DC_STATUS_INVALIDARGS; + + return device->socket->bytesAvailable(); +} + +static int qt_serial_get_transmitted(serial_t *device) +{ + if (device == NULL || device->socket == NULL) + return DC_STATUS_INVALIDARGS; + + return device->socket->bytesToWrite(); +} + + +const dc_serial_operations_t qt_serial_ops = { + .open = qt_serial_open, + .close = qt_serial_close, + .read = qt_serial_read, + .write = qt_serial_write, + .flush = qt_serial_flush, + .get_received = qt_serial_get_received, + .get_transmitted = qt_serial_get_transmitted +}; + +extern void dc_serial_init (dc_serial_t *serial, void *data, const dc_serial_operations_t *ops); + +dc_status_t dc_serial_qt_open(dc_serial_t **out, dc_context_t *context, const char *devaddr) +{ + if (out == NULL) + return DC_STATUS_INVALIDARGS; + + // Allocate memory. + dc_serial_t *serial_device = (dc_serial_t *) malloc (sizeof (dc_serial_t)); + + if (serial_device == NULL) { + return DC_STATUS_NOMEMORY; + } + + // Initialize data and function pointers + dc_serial_init(serial_device, NULL, &qt_serial_ops); + + // Open the serial device. + dc_status_t rc = (dc_status_t)qt_serial_open (&serial_device->port, context, devaddr); + if (rc != DC_STATUS_SUCCESS) { + free (serial_device); + return rc; + } + + // Set the type of the device + serial_device->type = DC_TRANSPORT_BLUETOOTH; + + *out = serial_device; + + return DC_STATUS_SUCCESS; +} +} -- 2.1.4
From 892f0aa6d21c5aa1eeba80b671717ab6202e42c3 Mon Sep 17 00:00:00 2001 From: Claudiu Olteanu <[email protected]> Date: Mon, 6 Jul 2015 17:11:02 +0300 Subject: [PATCH 4/4] Add extra logs for custom serial Bluetooth implementation This patch increases the verbosity level for QtBluetooth API and add some extra logs for custom serial Bluetooth open method. The scope of this patch is only for testing. Signed-off-by: Claudiu Olteanu <[email protected]> --- main.cpp | 3 ++- qtserialbluetooth.cpp | 6 ++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/main.cpp b/main.cpp index b05b802..747d2b2 100644 --- a/main.cpp +++ b/main.cpp @@ -15,6 +15,7 @@ #include <QStringList> #include <QApplication> +#include <QLoggingCategory> #include <git2.h> QTranslator *qtTranslator, *ssrfTranslator; @@ -23,7 +24,7 @@ int main(int argc, char **argv) { int i; bool no_filenames = true; - + QLoggingCategory::setFilterRules(QStringLiteral("qt.bluetooth* = true")); QApplication *application = new QApplication(argc, argv); QStringList files; QStringList importedFiles; diff --git a/qtserialbluetooth.cpp b/qtserialbluetooth.cpp index b718456..1f0ed6b 100644 --- a/qtserialbluetooth.cpp +++ b/qtserialbluetooth.cpp @@ -4,6 +4,7 @@ #include <QtBluetooth/QBluetoothSocket> #include <QEventLoop> #include <QTimer> +#include <QDebug> #include <libdivecomputer/custom_serial.h> @@ -57,16 +58,19 @@ static int qt_serial_open(serial_t **out, dc_context_t *context, const char* dev if (serial_port->socket->state() == QBluetoothSocket::ConnectingState) { // It seems that the connection on channel 1 took more than expected. Wait another 15 seconds + qDebug() << "The connection on RFCOMM channel number 1 took more than expected. Wait another 15 seconds."; timer.start(3 * msec); loop.exec(); } else if (serial_port->socket->state() == QBluetoothSocket::UnconnectedState) { // Try to connect on channel number 5. Maybe this is a Shearwater Petrel2 device. + qDebug() << "Connection on channel 1 failed. Trying on channel number 5."; serial_port->socket->connectToService(remoteDeviceAddress, 5); timer.start(msec); loop.exec(); if (serial_port->socket->state() == QBluetoothSocket::ConnectingState) { // It seems that the connection on channel 5 took more than expected. Wait another 15 seconds + qDebug() << "The connection on RFCOMM channel number 5 took more than expected. Wait another 15 seconds."; timer.start(3 * msec); loop.exec(); } @@ -77,6 +81,8 @@ static int qt_serial_open(serial_t **out, dc_context_t *context, const char* dev // Get the latest error and try to match it with one from libdivecomputer QBluetoothSocket::SocketError err = serial_port->socket->error(); + qDebug() << "Failed to connect to device " << devaddr << ". Device state " << serial_port->socket->state() << ". Error: " << err; + switch(err) { case QBluetoothSocket::HostNotFoundError: case QBluetoothSocket::ServiceNotFoundError: -- 2.1.4
_______________________________________________ subsurface mailing list [email protected] http://lists.subsurface-divelog.org/cgi-bin/mailman/listinfo/subsurface
