Begin adding support for Bluetooth

This commit is contained in:
Alex Spataru 2022-02-17 22:32:49 -06:00
parent 4d1fb85bf0
commit a9a9a176ef
9 changed files with 548 additions and 9 deletions

View File

@ -1,5 +1,5 @@
#
# Copyright (c) 2020-2022 Alex Spataru <https://github.com/alex-spataru>
# Copyright (c) 2020-2021 Alex Spataru <https://github.com/alex-spataru>
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
@ -48,6 +48,7 @@ QT += svg
QT += core
QT += quick
QT += widgets
QT += bluetooth
QT += serialport
QT += printsupport
QT += quickcontrols2
@ -159,6 +160,7 @@ HEADERS += \
src/DataTypes.h \
src/IO/Checksum.h \
src/IO/Console.h \
src/IO/DataSources/Bluetooth.h \
src/IO/DataSources/Network.h \
src/IO/DataSources/Serial.h \
src/IO/Manager.h \
@ -201,6 +203,7 @@ SOURCES += \
src/CSV/Player.cpp \
src/IO/Checksum.cpp \
src/IO/Console.cpp \
src/IO/DataSources/Bluetooth.cpp \
src/IO/DataSources/Network.cpp \
src/IO/DataSources/Serial.cpp \
src/IO/Manager.cpp \

View File

@ -59,6 +59,7 @@
<file>icons/points.svg</file>
<file>icons/power.svg</file>
<file>icons/ram.svg</file>
<file>icons/refresh.svg</file>
<file>icons/registration.svg</file>
<file>icons/restore.svg</file>
<file>icons/right.svg</file>
@ -114,6 +115,7 @@
<file>qml/JsonEditor/JsonDatasetDelegate.qml</file>
<file>qml/JsonEditor/JsonGroupDelegate.qml</file>
<file>qml/JsonEditor/TreeView.qml</file>
<file>qml/Panes/SetupPanes/Bluetooth.qml</file>
<file>qml/Panes/SetupPanes/MQTT.qml</file>
<file>qml/Panes/SetupPanes/Network.qml</file>
<file>qml/Panes/SetupPanes/Serial.qml</file>
@ -140,18 +142,23 @@
<file>qml/main.qml</file>
<file>themes/1_Flat.json</file>
<file>themes/2_Dark.json</file>
<file>themes/4_Midnight.json</file>
<file>themes/3_Classic.json</file>
<file>themes/4_Midnight.json</file>
<file>themes/5_Noir.json</file>
<file>themes/6_Mathworks.json</file>
<file>touchbar/console.png</file>
<file>touchbar/dashboard.png</file>
<file>touchbar/setup.png</file>
<file>translations/de.qm</file>
<file>translations/de.ts</file>
<file>translations/en.qm</file>
<file>translations/en.ts</file>
<file>translations/es.qm</file>
<file>translations/es.ts</file>
<file>translations/ru.qm</file>
<file>translations/ru.ts</file>
<file>translations/zh.qm</file>
<file>translations/zh.ts</file>
<file>window-border/macOS/close-active.svg</file>
<file>window-border/macOS/close-hover.svg</file>
<file>window-border/macOS/close-normal.svg</file>

1
assets/icons/refresh.svg Normal file
View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 0 24 24" width="24"><path d="M19 8l-4 4h3c0 3.31-2.69 6-6 6-1.01 0-1.97-.25-2.8-.7l-1.46 1.46C8.97 19.54 10.43 20 12 20c4.42 0 8-3.58 8-8h3l-4-4zM6 12c0-3.31 2.69-6 6-6 1.01 0 1.97.25 2.8.7l1.46-1.46C15.03 4.46 13.57 4 12 4c-4.42 0-8 3.58-8 8H1l4 4 4-4H6z"/><path d="M0 0h24v24H0z" fill="none"/></svg>

After

Width:  |  Height:  |  Size: 364 B

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2020-2022 Alex Spataru <https://github.com/alex-spataru>
* Copyright (c) 2020-2021 Alex Spataru <https://github.com/alex-spataru>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
@ -246,7 +246,7 @@ Item {
Layout.fillWidth: true
Layout.maximumWidth: root.maxItemWidth
onCurrentIndexChanged: {
if (currentIndex < 2 && currentIndex !== Cpp_IO_Manager.dataSource)
if (currentIndex < 3 && currentIndex !== Cpp_IO_Manager.dataSource)
Cpp_IO_Manager.dataSource = currentIndex
}
@ -262,6 +262,12 @@ Item {
width: implicitWidth + 2 * app.spacing
}
TabButton {
text: qsTr("Bluetooth")
height: tab.height + 3
width: implicitWidth + 2 * app.spacing
}
TabButton {
text: qsTr("MQTT")
height: tab.height + 3
@ -302,9 +308,12 @@ Item {
stack.implicitHeight = network.implicitHeight
break
case 2:
stack.implicitHeight = mqtt.implicitHeight
stack.implicitHeight = bluetooth.implicitHeight
break
case 3:
stack.implicitHeight = mqtt.implicitHeight
break
case 4:
stack.implicitHeight = settings.implicitHeight
break
default:
@ -323,10 +332,26 @@ Item {
SetupPanes.Network {
id: network
onUiChanged: timer.start()
onUiChanged: networkTimer.start()
Timer {
id: timer
id: networkTimer
interval: 50
onTriggered: stack.updateHeight()
}
background: TextField {
enabled: false
palette.base: Cpp_ThemeManager.setupPanelBackground
}
}
SetupPanes.Bluetooth {
id: bluetooth
onUiChanged: bluetoothTimer.start()
Timer {
id: bluetoothTimer
interval: 50
onTriggered: stack.updateHeight()
}

View File

@ -0,0 +1,175 @@
/*
* Copyright (c) 2020-2021 Alex Spataru <https://github.com/alex-spataru>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
import QtQuick 2.3
import QtQuick.Layouts 1.3
import QtQuick.Controls 2.3
import "../../Widgets" as Widgets
Control {
id: root
implicitHeight: layout.implicitHeight + app.spacing * 2
//
// Signals
//
signal uiChanged()
//
// Control layout
//
ColumnLayout {
id: layout
spacing: app.spacing
anchors.fill: parent
anchors.margins: app.spacing
//
// Device selector + refresh button
//
RowLayout {
spacing: app.spacing
Label {
id: devLabel
opacity: enabled ? 1 : 0.5
text: qsTr("Device") + ":"
enabled: !Cpp_IO_Manager.connected
Layout.minimumWidth: Math.max(devLabel.implicitWidth, servLabel.implicitWidth)
}
ComboBox {
id: _deviceCombo
Layout.fillWidth: true
opacity: enabled ? 1 : 0.5
model: Cpp_IO_Bluetooth.devices
enabled: !Cpp_IO_Manager.connected
onCurrentIndexChanged: {
if (currentIndex !== Cpp_IO_Bluetooth.currentDevice + 1)
Cpp_IO_Bluetooth.currentDevice = currentIndex
root.uiChanged()
}
}
Button {
width: 24
height: 24
icon.width: 16
icon.height: 16
opacity: enabled ? 1 : 0.5
icon.color: Cpp_ThemeManager.text
icon.source: "qrc:/icons/refresh.svg"
onClicked: Cpp_IO_Bluetooth.beginScanning()
}
}
//
// Service selector
//
RowLayout {
spacing: app.spacing
enabled: Cpp_IO_Bluetooth.supported
visible: Cpp_IO_Bluetooth.supported
Label {
id: servLabel
opacity: enabled ? 1 : 0.5
text: qsTr("Service") + ":"
enabled: !Cpp_IO_Manager.connected
Layout.minimumWidth: Math.max(devLabel.implicitWidth, servLabel.implicitWidth)
}
ComboBox {
id: _service
Layout.fillWidth: true
opacity: enabled ? 1 : 0.5
model: Cpp_IO_Bluetooth.services
enabled: !Cpp_IO_Manager.connected
}
}
//
// RSSI
//
RowLayout {
spacing: app.spacing
Label {
id: rssiLabel
opacity: enabled ? 1 : 0.5
text: qsTr("RSSI") + ":"
enabled: !Cpp_IO_Manager.connected
Layout.minimumWidth: Math.max(devLabel.implicitWidth, servLabel.implicitWidth)
}
Label {
id: _rssi
Layout.fillWidth: true
opacity: enabled ? 1 : 0.5
text: Cpp_IO_Bluetooth.rssi
enabled: !Cpp_IO_Manager.connected
}
}
//
// Dinamic spacer
//
Item {
height: app.spacing
visible: !Cpp_IO_Bluetooth.supported && Cpp_IO_Bluetooth.currentDevice >= 0
}
//
// Not at BLE device warning
//
RowLayout {
spacing: app.spacing
visible: !Cpp_IO_Bluetooth.supported && Cpp_IO_Bluetooth.currentDevice >= 0
Widgets.Icon {
width: 18
height: 18
color: palette.text
source: "qrc:/icons/warning.svg"
Layout.alignment: Qt.AlignHCenter
}
Label {
Layout.fillWidth: true
wrapMode: Label.WordWrap
text: "<b>" + qsTr("Note:") + "</b> " + qsTr("This is not a BLE device, I/O operations " +
"are not yet supported for classic Bluetooth devices.")
}
}
//
// Spacer
//
Item {
Layout.fillHeight: true
}
}
}

View File

@ -0,0 +1,216 @@
/*
* Copyright (c) 2022 Alex Spataru <https://github.com/alex-spataru>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#include "Bluetooth.h"
/**
* Constructor function
*/
IO::DataSources::Bluetooth::Bluetooth()
{
// Configure signals/slots
connect(&m_discovery, &QBluetoothDeviceDiscoveryAgent::deviceUpdated, this,
&IO::DataSources::Bluetooth::deviceUpdated);
connect(&m_discovery, &QBluetoothDeviceDiscoveryAgent::deviceDiscovered, this,
&IO::DataSources::Bluetooth::deviceDiscovered);
// Start the device discovery process
QTimer::singleShot(1000, this, &IO::DataSources::Bluetooth::beginScanning);
}
/**
* Destructor function
*/
IO::DataSources::Bluetooth::~Bluetooth() { }
/**
* Returns a reference to the only instance of the class
*/
IO::DataSources::Bluetooth &IO::DataSources::Bluetooth::instance()
{
static Bluetooth instance;
return instance;
}
/**
* Returns the RSSI of the current device
*/
QString IO::DataSources::Bluetooth::rssi() const
{
return m_rssi;
}
/**
* Returns @c true if current device is a BLE device.
*/
bool IO::DataSources::Bluetooth::supported() const
{
return m_supported;
}
/**
* Returns the index of the currently selected device
*/
int IO::DataSources::Bluetooth::currentDevice() const
{
return m_currentDevice;
}
/**
* Returns a list with the names of all the devices discovered so far
*/
QStringList IO::DataSources::Bluetooth::devices() const
{
return m_devices;
}
/**
* Returns a list with the names of the services associated with the
* current device.
*/
QStringList IO::DataSources::Bluetooth::services() const
{
return m_services;
}
/**
* Starts the device discovery process for both classic and low energy
* bluetooth devices.
*/
void IO::DataSources::Bluetooth::beginScanning()
{
// Reset device & service lists
m_devices.clear();
m_services.clear();
// Reset parameters for current device
m_rssi = tr("N/A");
m_supported = false;
m_currentDevice = -1;
m_services.append(tr("N/A"));
m_devices.append(tr("Searching..."));
// Update UI
Q_EMIT rssiChanged();
Q_EMIT devicesChanged();
Q_EMIT servicesChanged();
Q_EMIT supportedChanged();
Q_EMIT currentDeviceChanged();
// Re-start device discovery
m_discovery.stop();
m_discovery.start();
}
/**
* Changes the current device index & obtains the services associated
* for the given device.
*/
void IO::DataSources::Bluetooth::setCurrentDevice(const int index)
{
// Reset device parameters
m_services.clear();
m_currentDevice = index - 1;
// Get device info
if (m_currentDevice >= 0)
{
// Get device info object
auto device = m_discovery.discoveredDevices().at(m_currentDevice);
// Update device information
m_services.append("No services found");
m_rssi = QString::number(device.rssi()) + " dB";
m_supported = device.coreConfigurations()
& QBluetoothDeviceInfo::LowEnergyCoreConfiguration;
}
// Not a valid device, reset data
else
{
m_rssi = tr("N/A");
m_supported = false;
m_services.append(tr("N/A"));
}
// Service list is empty, add dummy message for the user
if (m_services.isEmpty())
{
if (currentDevice() >= 0)
m_services.append(tr("Requesting services..."));
else
m_services.append(tr("Waiting for device selection..."));
}
// Update UI
Q_EMIT rssiChanged();
Q_EMIT servicesChanged();
Q_EMIT supportedChanged();
Q_EMIT currentDeviceChanged();
}
/**
* Refreshes the bluetooth device list
*/
void IO::DataSources::Bluetooth::refreshDevices()
{
// Clear devices
m_devices.clear();
m_services.clear();
m_devices.append(tr("Select device"));
// Add all discovered devices to list
for (int i = 0; i < m_discovery.discoveredDevices().count(); ++i)
{
auto name = m_discovery.discoveredDevices().at(i).name();
m_devices.append(name);
}
// Update UI
Q_EMIT rssiChanged();
Q_EMIT devicesChanged();
Q_EMIT servicesChanged();
}
/**
* Re-generate device list uppon device discovery
*/
void IO::DataSources::Bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &info)
{
(void)info;
refreshDevices();
}
/**
* Re-generate device list uppon device update
*/
void IO::DataSources::Bluetooth::deviceUpdated(const QBluetoothDeviceInfo &info,
QBluetoothDeviceInfo::Fields updatedFields)
{
(void)info;
(void)updatedFields;
refreshDevices();
}
#ifdef SERIAL_STUDIO_INCLUDE_MOC
# include "moc_Bluetooth.cpp"
#endif

View File

@ -0,0 +1,107 @@
/*
* Copyright (c) 2022 Alex Spataru <https://github.com/alex-spataru>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#pragma once
#include <DataTypes.h>
#include <QObject>
#include <QBluetoothUuid>
#include <QBluetoothDeviceInfo>
#include <QBluetoothDeviceDiscoveryAgent>
namespace IO
{
namespace DataSources
{
/**
* @brief The Bluetooth class
*
* Serial Studio "driver" class to interact with Bluetooth devices.
*/
class Bluetooth : public QObject
{
// clang-format off
Q_OBJECT
Q_PROPERTY(QStringList devices
READ devices
NOTIFY devicesChanged)
Q_PROPERTY(QStringList services
READ services
NOTIFY servicesChanged)
Q_PROPERTY(int currentDevice
READ currentDevice
WRITE setCurrentDevice
NOTIFY currentDeviceChanged)
Q_PROPERTY(QString rssi
READ rssi
NOTIFY rssiChanged)
Q_PROPERTY(bool supported
READ supported
NOTIFY supportedChanged)
// clang-format on
Q_SIGNALS:
void rssiChanged();
void devicesChanged();
void servicesChanged();
void supportedChanged();
void currentDeviceChanged();
private:
explicit Bluetooth();
Bluetooth(Bluetooth &&) = delete;
Bluetooth(const Bluetooth &) = delete;
Bluetooth &operator=(Bluetooth &&) = delete;
Bluetooth &operator=(const Bluetooth &) = delete;
~Bluetooth();
public:
static Bluetooth &instance();
QString rssi() const;
bool supported() const;
int currentDevice() const;
QStringList devices() const;
QStringList services() const;
public Q_SLOTS:
void beginScanning();
void setCurrentDevice(const int index);
private Q_SLOTS:
void refreshDevices();
void deviceDiscovered(const QBluetoothDeviceInfo &info);
void deviceUpdated(const QBluetoothDeviceInfo &info,
QBluetoothDeviceInfo::Fields updatedFields);
private:
QString m_rssi;
bool m_supported;
int m_currentDevice;
QStringList m_devices;
QStringList m_services;
QBluetoothDeviceDiscoveryAgent m_discovery;
};
}
}

View File

@ -35,6 +35,7 @@
#include <IO/Console.h>
#include <IO/DataSources/Serial.h>
#include <IO/DataSources/Network.h>
#include <IO/DataSources/Bluetooth.h>
#include <Misc/MacExtras.h>
#include <Misc/Utilities.h>
@ -180,6 +181,7 @@ void Misc::ModuleManager::initializeQmlInterface()
auto miscTimerEvents = &Misc::TimerEvents::instance();
auto ioNetwork = &IO::DataSources::Network::instance();
auto miscThemeManager = &Misc::ThemeManager::instance();
auto ioBluetooth = &IO::DataSources::Bluetooth::instance();
// Initialize third-party modules
auto updater = QSimpleUpdater::getInstance();
@ -225,6 +227,7 @@ void Misc::ModuleManager::initializeQmlInterface()
c->setContextProperty("Cpp_JSON_Editor", jsonEditor);
c->setContextProperty("Cpp_MQTT_Client", mqttClient);
c->setContextProperty("Cpp_UI_Dashboard", uiDashboard);
c->setContextProperty("Cpp_IO_Bluetooth", ioBluetooth);
c->setContextProperty("Cpp_JSON_Generator", jsonGenerator);
c->setContextProperty("Cpp_Plugins_Bridge", pluginsBridge);
c->setContextProperty("Cpp_Misc_MacExtras", miscMacExtras);

View File

@ -241,8 +241,9 @@
#include "CSV/Player.h"
#include "IO/Checksum.h"
#include "IO/Console.h"
#include "IO/DataSources/Network.h"
#include "IO/DataSources/Serial.h"
#include "IO/DataSources/Network.h"
#include "IO/DataSources/Bluetooth.h"
#include "IO/Manager.h"
#include "JSON/Dataset.h"
#include "JSON/Editor.h"
@ -466,8 +467,9 @@
#include "CSV/Player.cpp"
#include "IO/Checksum.cpp"
#include "IO/Console.cpp"
#include "IO/DataSources/Network.cpp"
#include "IO/DataSources/Serial.cpp"
#include "IO/DataSources/Network.cpp"
#include "IO/DataSources/Bluetooth.cpp"
#include "IO/Manager.cpp"
#include "JSON/Dataset.cpp"
#include "JSON/Editor.cpp"