From 35c1c07cd676b9c9afb23ad7266a67d5b6aef03a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kari=20Hautam=C3=A4ki?= Date: Thu, 16 Feb 2017 08:33:49 +0200 Subject: iot-sensortag: Allow reconnect after a failed connection attempt A disconnected sensor tag can be tried to be reconnected by clicking on the chart area or starting rescan from the Sensor settings menu. During a (re)connection process a progress indicator is shown to the user. The user has a possibility to disconnect (and reconnect) a currently connect SensorDataProvider A new state, 'NotFound' is added in SensorTagDataProvider to distinguish between states when a Sensor Tag has not been discovered at all, and when it has already been discovered but is has been disconnected. Change-Id: I54eea72d8c92a67a5ccbb3bc192ac8f33ada1c39 Reviewed-by: Maurice Kalinowski --- tradeshow/iot-sensortag/base.qrc | 1 + tradeshow/iot-sensortag/bluetoothdataprovider.cpp | 56 ++++++--- tradeshow/iot-sensortag/bluetoothdataprovider.h | 3 +- tradeshow/iot-sensortag/bluetoothdevice.cpp | 59 ++++++--- tradeshow/iot-sensortag/bluetoothdevice.h | 2 + tradeshow/iot-sensortag/dataproviderpool.cpp | 5 + tradeshow/iot-sensortag/dataproviderpool.h | 2 + tradeshow/iot-sensortag/demodataproviderpool.cpp | 67 +++++++---- tradeshow/iot-sensortag/demodataproviderpool.h | 2 +- tradeshow/iot-sensortag/main.cpp | 6 +- tradeshow/iot-sensortag/mockdataprovider.cpp | 5 + tradeshow/iot-sensortag/mockdataprovider.h | 1 + .../iot-sensortag/resources/base/BaseChart.qml | 44 +++++++ .../iot-sensortag/resources/base/BlinkingIcon.qml | 72 +++++++++++ .../iot-sensortag/resources/base/RotationPage.qml | 128 +++++++++++++------- .../resources/base/SensorSettings.qml | 133 +++++++++++++++------ tradeshow/iot-sensortag/resources/base/main.qml | 37 ++++++ .../iot-sensortag/resources/small/MainSmall.qml | 47 +++++--- tradeshow/iot-sensortag/sensortagdataprovider.cpp | 2 +- tradeshow/iot-sensortag/sensortagdataprovider.h | 2 +- .../iot-sensortag/sensortagdataproviderpool.cpp | 55 ++++++--- .../iot-sensortag/sensortagdataproviderpool.h | 10 +- 22 files changed, 561 insertions(+), 178 deletions(-) create mode 100644 tradeshow/iot-sensortag/resources/base/BlinkingIcon.qml (limited to 'tradeshow') diff --git a/tradeshow/iot-sensortag/base.qrc b/tradeshow/iot-sensortag/base.qrc index f0fddae..db038ef 100644 --- a/tradeshow/iot-sensortag/base.qrc +++ b/tradeshow/iot-sensortag/base.qrc @@ -21,5 +21,6 @@ resources/base/images/bg_blue.jpg resources/base/RotationPage.qml resources/base/AltitudeChart.qml + resources/base/BlinkingIcon.qml diff --git a/tradeshow/iot-sensortag/bluetoothdataprovider.cpp b/tradeshow/iot-sensortag/bluetoothdataprovider.cpp index e3c746c..0987d0d 100644 --- a/tradeshow/iot-sensortag/bluetoothdataprovider.cpp +++ b/tradeshow/iot-sensortag/bluetoothdataprovider.cpp @@ -57,32 +57,42 @@ Q_DECLARE_LOGGING_CATEGORY(boot2QtDemos) BluetoothDataProvider::BluetoothDataProvider(QString id, QObject *parent) : SensorTagDataProvider(id, parent) - , activeDevice(Q_NULLPTR) + , m_btDevice(Q_NULLPTR) , m_smaSamples(0) , m_zeroAltitudeSamples(0) , gyroscopeX_calibration(0) , gyroscopeY_calibration(0) , gyroscopeZ_calibration(0) { + m_state = NotFound; intervalRotation = SENSORTAG_RAPID_TIMER_TIMEOUT_MS; } BluetoothDataProvider::~BluetoothDataProvider() { - + if (m_btDevice) + delete m_btDevice; } bool BluetoothDataProvider::startDataFetching() { qCDebug(boot2QtDemos) << Q_FUNC_INFO; - if (activeDevice) { - connect(activeDevice, &BluetoothDevice::statusUpdated, this, [](const QString& statusMsg) { qCDebug(boot2QtDemos) << "----------" << statusMsg; }); - connect(activeDevice, &BluetoothDevice::stateChanged, this, &BluetoothDataProvider::updateState); - connect(activeDevice, &BluetoothDevice::temperatureChanged, this, &BluetoothDataProvider::temperatureReceived); - connect(activeDevice, &BluetoothDevice::barometerChanged, this, &BluetoothDataProvider::barometerReceived); - connect(activeDevice, &BluetoothDevice::humidityChanged, this, &BluetoothDataProvider::humidityReceived); - connect(activeDevice, &BluetoothDevice::lightIntensityChanged, this, &BluetoothDataProvider::lightIntensityReceived); - connect(activeDevice, &BluetoothDevice::motionChanged, this, &BluetoothDataProvider::motionReceived); + if (m_btDevice) { + connect(m_btDevice, &BluetoothDevice::statusUpdated, this, [=](const QString& statusMsg) { + qCDebug(boot2QtDemos) << id() << "----------" << statusMsg; + }); + connect(m_btDevice, &BluetoothDevice::stateChanged, + this, &BluetoothDataProvider::updateState); + connect(m_btDevice, &BluetoothDevice::temperatureChanged, + this, &BluetoothDataProvider::temperatureReceived); + connect(m_btDevice, &BluetoothDevice::barometerChanged, + this, &BluetoothDataProvider::barometerReceived); + connect(m_btDevice, &BluetoothDevice::humidityChanged, + this, &BluetoothDataProvider::humidityReceived); + connect(m_btDevice, &BluetoothDevice::lightIntensityChanged, + this, &BluetoothDataProvider::lightIntensityReceived); + connect(m_btDevice, &BluetoothDevice::motionChanged, + this, &BluetoothDataProvider::motionReceived); timer.setInterval(1000); timer.setSingleShot(true); connect(&timer, &QTimer::timeout, this, &BluetoothDataProvider::startServiceScan); @@ -98,8 +108,10 @@ void BluetoothDataProvider::endDataFetching() void BluetoothDataProvider::startServiceScan() { qCDebug(boot2QtDemos)<scanServices(); + if (m_btDevice) { + setState(Scanning); + m_btDevice->scanServices(); + } } void BluetoothDataProvider::temperatureReceived(double newAmbientTemperature, double newObjectTemperature) @@ -213,9 +225,9 @@ QString BluetoothDataProvider::versionString() const void BluetoothDataProvider::updateState() { - switch (activeDevice->state()) { + switch (m_btDevice->state()) { case BluetoothDevice::Disconnected: - setState(Disconnected); + unbindDevice(); break; case BluetoothDevice::Scanning: setState(Scanning); @@ -262,11 +274,21 @@ void BluetoothDataProvider::recalibrateZeroAltitude() void BluetoothDataProvider::bindToDevice(BluetoothDevice *device) { - activeDevice = device; - startDataFetching(); + if (m_btDevice) + delete m_btDevice; + m_btDevice = device; +} + +void BluetoothDataProvider::unbindDevice() +{ + if (m_btDevice) { + delete m_btDevice; + m_btDevice = 0; + setState(NotFound); + } } BluetoothDevice *BluetoothDataProvider::device() { - return activeDevice; + return m_btDevice; } diff --git a/tradeshow/iot-sensortag/bluetoothdataprovider.h b/tradeshow/iot-sensortag/bluetoothdataprovider.h index f4c4b15..691b94c 100644 --- a/tradeshow/iot-sensortag/bluetoothdataprovider.h +++ b/tradeshow/iot-sensortag/bluetoothdataprovider.h @@ -70,6 +70,7 @@ public: QString versionString() const; void bindToDevice(BluetoothDevice *device); + void unbindDevice(); BluetoothDevice* device(); public slots: @@ -88,7 +89,7 @@ protected: private: void updateState(); float countRotationDegrees(double degreesPerSecond, quint64 milliseconds); - BluetoothDevice* activeDevice; + BluetoothDevice *m_btDevice; QTimer timer; int m_smaSamples; int m_zeroAltitudeSamples; diff --git a/tradeshow/iot-sensortag/bluetoothdevice.cpp b/tradeshow/iot-sensortag/bluetoothdevice.cpp index bc1fc0a..667e7a2 100644 --- a/tradeshow/iot-sensortag/bluetoothdevice.cpp +++ b/tradeshow/iot-sensortag/bluetoothdevice.cpp @@ -84,6 +84,17 @@ BluetoothDevice::BluetoothDevice() { m_lastMilliseconds = QDateTime::currentMSecsSinceEpoch(); statusUpdated("Device created"); + + m_serviceDetailsTimer = new QTimer(this); + m_serviceDetailsTimer->setInterval(5000); + m_serviceDetailsTimer->setSingleShot(true); + connect(m_serviceDetailsTimer, &QTimer::timeout, [=]() { + qCDebug(boot2QtDemos) << "Service details timer timeout. No more services details found"; + if (isDeviceReady()) + setState(Connected); + else + setState(Disconnected); + }); } BluetoothDevice::BluetoothDevice(const QBluetoothDeviceInfo &d) @@ -94,8 +105,12 @@ BluetoothDevice::BluetoothDevice(const QBluetoothDeviceInfo &d) BluetoothDevice::~BluetoothDevice() { - delete m_controller; + if (m_controller) { + m_controller->disconnect(); + delete m_controller; + } } + QString BluetoothDevice::getAddress() const { #if defined(Q_OS_DARWIN) @@ -114,6 +129,7 @@ QString BluetoothDevice::getName() const void BluetoothDevice::scanServices() { + setState(Scanning); if (!m_controller) { statusUpdated("(Connecting to device...)"); // Connecting signals and slots for connecting to LE services. @@ -131,6 +147,8 @@ void BluetoothDevice::scanServices() m_controller->setRemoteAddressType(QLowEnergyController::PublicAddress); m_controller->connectToDevice(); + } else { + deviceConnected(); } } @@ -209,6 +227,8 @@ void BluetoothDevice::serviceScanDone() if (!m_motionService) qCDebug(boot2QtDemos) << "Motion service not found."; + + m_serviceDetailsTimer->start(); } void BluetoothDevice::temperatureDetailsDiscovered(QLowEnergyService::ServiceState newstate) @@ -247,8 +267,7 @@ void BluetoothDevice::temperatureDetailsDiscovered(QLowEnergyService::ServiceSta } m_temperatureMeasurementStarted = true; - if (isDeviceReady()) - setState(DeviceState::Connected); + m_serviceDetailsTimer->start(); } } @@ -287,8 +306,7 @@ void BluetoothDevice::barometerDetailsDiscovered(QLowEnergyService::ServiceState } m_barometerMeasurementStarted = true; - if (isDeviceReady()) - setState(DeviceState::Connected); + m_serviceDetailsTimer->start(); } } @@ -328,8 +346,7 @@ void BluetoothDevice::humidityDetailsDiscovered(QLowEnergyService::ServiceState } m_humidityMeasurementStarted = true; - if (isDeviceReady()) - setState(DeviceState::Connected); + m_serviceDetailsTimer->start(); } } @@ -369,8 +386,7 @@ void BluetoothDevice::lightIntensityDetailsDiscovered(QLowEnergyService::Service } m_lightIntensityMeasurementStarted = true; - if (isDeviceReady()) - setState(DeviceState::Connected); + m_serviceDetailsTimer->start(); } } @@ -412,8 +428,7 @@ void BluetoothDevice::motionDetailsDiscovered(QLowEnergyService::ServiceState ne m_motionService->writeCharacteristic(characteristic, QByteArray::fromHex(SENSORTAG_RAPID_TIMER_TIMEOUT_STR), QLowEnergyService::WriteWithResponse); } m_motionMeasurementStarted = true; - if (isDeviceReady()) - setState(DeviceState::Connected); + m_serviceDetailsTimer->start(); } } @@ -461,10 +476,14 @@ double BluetoothDevice::convertIrTemperatureAPIReadingToCelsius(quint16 rawReadi bool BluetoothDevice::isDeviceReady() const { - return m_temperatureMeasurementStarted - && m_humidityMeasurementStarted - && m_lightIntensityMeasurementStarted - && m_motionMeasurementStarted; + if (m_temperatureMeasurementStarted + || m_barometerMeasurementStarted + || m_humidityMeasurementStarted + || m_lightIntensityMeasurementStarted + || m_motionMeasurementStarted) { + return true; + } + return false; } void BluetoothDevice::irTemperatureReceived(const QByteArray &value) @@ -552,9 +571,13 @@ void BluetoothDevice::motionReceived(const QByteArray &value) void BluetoothDevice::deviceConnected() { - setState(DeviceState::Scanning); - statusUpdated("(Discovering services...)"); - m_controller->discoverServices(); + if (isDeviceReady()) { + setState(Connected); + } else { + setState(DeviceState::Scanning); + statusUpdated("(Discovering services...)"); + m_controller->discoverServices(); + } } void BluetoothDevice::errorReceived(QLowEnergyController::Error /*error*/) diff --git a/tradeshow/iot-sensortag/bluetoothdevice.h b/tradeshow/iot-sensortag/bluetoothdevice.h index 1a37448..6a75a67 100644 --- a/tradeshow/iot-sensortag/bluetoothdevice.h +++ b/tradeshow/iot-sensortag/bluetoothdevice.h @@ -185,6 +185,8 @@ private: QBluetoothDeviceInfo m_deviceInfo; SensorTagDataProvider *m_dataProvider; + + QTimer *m_serviceDetailsTimer; }; #endif // BLUETOOTHBLUETOOTHDEVICE_H diff --git a/tradeshow/iot-sensortag/dataproviderpool.cpp b/tradeshow/iot-sensortag/dataproviderpool.cpp index 040809d..77c4514 100644 --- a/tradeshow/iot-sensortag/dataproviderpool.cpp +++ b/tradeshow/iot-sensortag/dataproviderpool.cpp @@ -84,6 +84,11 @@ SensorTagDataProvider *DataProviderPool::getProvider(SensorTagDataProvider::TagT return p; } +void DataProviderPool::disconnectProvider(QString id) +{ + Q_UNUSED(id) +} + QQmlListProperty DataProviderPool::dataProviders() { return QQmlListProperty(this, m_dataProviders); diff --git a/tradeshow/iot-sensortag/dataproviderpool.h b/tradeshow/iot-sensortag/dataproviderpool.h index e8d50e6..9c14504 100644 --- a/tradeshow/iot-sensortag/dataproviderpool.h +++ b/tradeshow/iot-sensortag/dataproviderpool.h @@ -65,6 +65,7 @@ public: Q_INVOKABLE virtual void startScanning(); Q_INVOKABLE virtual void stopScanning(); Q_INVOKABLE virtual SensorTagDataProvider* getProvider(SensorTagDataProvider::TagType type) const; + Q_INVOKABLE virtual void disconnectProvider(QString id); QQmlListProperty dataProviders(); @@ -75,6 +76,7 @@ signals: void providerDisconnected(QString id); void providerInError(QString id); void providersUpdated(); + void scanStarted(); void scanFinished(); void providerForCloudChanged(); void dataProvidersChanged(); diff --git a/tradeshow/iot-sensortag/demodataproviderpool.cpp b/tradeshow/iot-sensortag/demodataproviderpool.cpp index ccaf7fd..92c5e41 100644 --- a/tradeshow/iot-sensortag/demodataproviderpool.cpp +++ b/tradeshow/iot-sensortag/demodataproviderpool.cpp @@ -49,6 +49,7 @@ ****************************************************************************/ #include "demodataproviderpool.h" #include "mockdataprovider.h" +#include "bluetoothdataprovider.h" DemoCloudProvider::DemoCloudProvider(QObject *parent) : SensorTagDataProvider(parent) @@ -261,15 +262,16 @@ DemoDataProviderPool::DemoDataProviderPool(QObject *parent) : SensorTagDataProviderPool("Demo", parent) , m_mockData(false) , m_cloudProvider(0) + , m_initialized(false) { } void DemoDataProviderPool::startScanning() { - qDeleteAll(m_dataProviders); - m_dataProviders.clear(); - if (m_mockData) { + qDeleteAll(m_dataProviders); + m_dataProviders.clear(); + MockDataProvider* p = new MockDataProvider("MOCK_PROVIDER_1", this); p->setTagType(SensorTagDataProvider::ObjectTemperature | SensorTagDataProvider::AmbientTemperature | SensorTagDataProvider::Rotation); m_dataProviders.push_back(p); @@ -287,6 +289,42 @@ void DemoDataProviderPool::startScanning() finishScanning(); } else { + if (!m_initialized) { + // Create a DataProvider objects early on for UI to be able to + // show all data providers as available. The BT device information + // will be added later on when the Bluetooth device has been found + for (const QString& id : m_macFilters) { + BluetoothDataProvider *p = new BluetoothDataProvider(id, this); + m_dataProviders.push_back(p); + } + // Fake that we have set of sensors with different capabilities + // by removing some of the sensor data types from each sensor tag + if (m_dataProviders.length() > 0) { + SensorTagDataProvider *p = m_dataProviders.at(0); + p->setTagType(SensorTagDataProvider::ObjectTemperature | + SensorTagDataProvider::Light | + SensorTagDataProvider::Magnetometer | + SensorTagDataProvider::Accelometer); + emit dataProvidersChanged(); + if (m_dataProviders.length() > 1) { + p = m_dataProviders.at(1); + p->setTagType(SensorTagDataProvider::AmbientTemperature | + SensorTagDataProvider::Altitude | + SensorTagDataProvider::Humidity | + SensorTagDataProvider::Rotation | + SensorTagDataProvider::AirPressure); + emit dataProvidersChanged(); + } + } + m_initialized = true; + } + // Set initial state to Scanning for UI to be + // able to show "Connecting.." information + for (SensorTagDataProvider *p : m_dataProviders) { + if (p->state() == SensorTagDataProvider::NotFound) { + p->setState(SensorTagDataProvider::Scanning); + } + } SensorTagDataProviderPool::startScanning(); } } @@ -299,25 +337,13 @@ void DemoDataProviderPool::finishScanning() m_cloudProvider = m_dataProviders.at(0); emit providerForCloudChanged(); } else { - // Fake that we have set of sensors with different capabilities - // by removing some of the sensor data types from each sensor tag - for (SensorTagDataProvider *p : m_dataProviders) { - if (p->id() == QStringLiteral("A0:E6:F8:B6:5B:86")) { - p->setTagType(SensorTagDataProvider::ObjectTemperature | - SensorTagDataProvider::Light | - SensorTagDataProvider::Magnetometer | - SensorTagDataProvider::Accelometer); - } - else if (p->id() == QStringLiteral("A0:E6:F8:B6:44:01")) { - p->setTagType(SensorTagDataProvider::AmbientTemperature | - SensorTagDataProvider::Altitude | - SensorTagDataProvider::Humidity | - SensorTagDataProvider::Rotation | - SensorTagDataProvider::AirPressure); - } - } m_cloudProvider = new DemoCloudProvider(this); static_cast(m_cloudProvider)->setDataProviders(m_dataProviders); + for (SensorTagDataProvider *p : m_dataProviders) { + // If BluetoothDevice object is not attached, the device has not been found + if (!static_cast(p)->device()) + p->setState(SensorTagDataProvider::NotFound); + } } emit dataProvidersChanged(); emit scanFinished(); @@ -332,4 +358,3 @@ SensorTagDataProvider *DemoDataProviderPool::providerForCloud() const { return m_cloudProvider; } - diff --git a/tradeshow/iot-sensortag/demodataproviderpool.h b/tradeshow/iot-sensortag/demodataproviderpool.h index 24a2136..7d2e939 100644 --- a/tradeshow/iot-sensortag/demodataproviderpool.h +++ b/tradeshow/iot-sensortag/demodataproviderpool.h @@ -70,9 +70,9 @@ protected: void finishScanning() override; private: - bool m_mockData; SensorTagDataProvider* m_cloudProvider; + bool m_initialized; }; // Internal class to provide sensor data for cloud update from demo setup diff --git a/tradeshow/iot-sensortag/main.cpp b/tradeshow/iot-sensortag/main.cpp index f2cf849..0925a5a 100644 --- a/tradeshow/iot-sensortag/main.cpp +++ b/tradeshow/iot-sensortag/main.cpp @@ -110,10 +110,10 @@ int main(int argc, char *argv[]) else if (sensorSource == QString("sensor").toLower()) { qCDebug(boot2QtDemos) << "Running in sensor mode"; dataProviderPool = new DemoDataProviderPool; - // List of devices used at EW: "A0:E6:F8:B6:44:01", "A0:E6:F8:B6:5B:86" + // List of devices used in Embedded World static_cast(dataProviderPool)->setMacFilterList(QStringList() << - "A0:E6:F8:B6:44:01" << - "A0:E6:F8:B6:5B:86"); + "24:71:89:BF:3B:82" << + "24:71:89:BC:44:82"); } #endif else if (sensorSource == QString("mock").toLower()){ diff --git a/tradeshow/iot-sensortag/mockdataprovider.cpp b/tradeshow/iot-sensortag/mockdataprovider.cpp index 4991e43..060cd70 100644 --- a/tradeshow/iot-sensortag/mockdataprovider.cpp +++ b/tradeshow/iot-sensortag/mockdataprovider.cpp @@ -238,6 +238,11 @@ void MockDataProvider::rapidTimerExpired() emit gyroscopeDegPerSecChanged(); } +void MockDataProvider::startServiceScan() +{ + +} + void MockDataProvider::reset() { rotation_x = 0; diff --git a/tradeshow/iot-sensortag/mockdataprovider.h b/tradeshow/iot-sensortag/mockdataprovider.h index 7f2c032..0a2ee19 100644 --- a/tradeshow/iot-sensortag/mockdataprovider.h +++ b/tradeshow/iot-sensortag/mockdataprovider.h @@ -71,6 +71,7 @@ public: public slots: void slowTimerExpired(); void rapidTimerExpired(); + void startServiceScan(); protected: void reset() override; diff --git a/tradeshow/iot-sensortag/resources/base/BaseChart.qml b/tradeshow/iot-sensortag/resources/base/BaseChart.qml index 58549b5..d144916 100644 --- a/tradeshow/iot-sensortag/resources/base/BaseChart.qml +++ b/tradeshow/iot-sensortag/resources/base/BaseChart.qml @@ -61,9 +61,17 @@ Item { property bool rightSide: false property alias titlePaneHeight: titleIcon.height property bool hasData: baseChart.sensor ? baseChart.sensor.state === SensorTagData.Connected : false + property int sensorState: sensor ? sensor.state : SensorTagData.NotFound signal clicked + onSensorStateChanged: { + if (sensorState === SensorTagData.Scanning) + sensorIcon.startBlinking(); + else + sensorIcon.stopBlinking(); + } + Image { id: titleIcon @@ -89,6 +97,42 @@ Item { anchors.bottomMargin: 16 anchors.left: parent.left anchors.right: parent.right + visible: sensorState === SensorTagData.Connected + } + + Item { + anchors.top: titleIcon.bottom + anchors.bottom: separator.bottom + anchors.bottomMargin: 16 + anchors.left: parent.left + anchors.right: parent.right + visible: sensorState === SensorTagData.Scanning || + sensorState === SensorTagData.Disconnected || + sensorState === SensorTagData.Error || + sensorState === SensorTagData.NotFound + + Column { + anchors.centerIn: parent + spacing: 20 + + BlinkingIcon { + id: sensorIcon + + source: pathPrefix + "Toolbar/icon_topbar_sensor.png" + anchors.horizontalCenter: parent.horizontalCenter + visible: sensorState === SensorTagData.Scanning + } + + Text { + id: stateText + + color: "white" + text: sensorState === SensorTagData.Scanning ? "Connecting to sensor..." + : sensorState === SensorTagData.Disconnected ? "Disconnected" + : sensorState === SensorTagData.NotFound ? "Device not found" + : "" + } + } } Image { diff --git a/tradeshow/iot-sensortag/resources/base/BlinkingIcon.qml b/tradeshow/iot-sensortag/resources/base/BlinkingIcon.qml new file mode 100644 index 0000000..fc29fc9 --- /dev/null +++ b/tradeshow/iot-sensortag/resources/base/BlinkingIcon.qml @@ -0,0 +1,72 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the examples of Qt for Device Creation. +** +** $QT_BEGIN_LICENSE:BSD$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** BSD License Usage +** Alternatively, you may use this file under the terms of the BSD license +** as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +import QtQuick 2.0 + +Image { + id: icon + + property bool activeBlink: false + + function startBlinking() { + if (!activeBlink) { + activeBlink = true; + mainWindow.startBlink(); + } + } + + function stopBlinking() { + if (activeBlink) { + activeBlink = false; + mainWindow.stopBlink(); + } + } + + opacity: activeBlink ? mainWindow.globalBlinkOpacity: 1 +} diff --git a/tradeshow/iot-sensortag/resources/base/RotationPage.qml b/tradeshow/iot-sensortag/resources/base/RotationPage.qml index 347a886..eee47c1 100644 --- a/tradeshow/iot-sensortag/resources/base/RotationPage.qml +++ b/tradeshow/iot-sensortag/resources/base/RotationPage.qml @@ -56,55 +56,101 @@ Item { property var sensor property var rotationUpdateInterval: sensor ? sensor.rotationUpdateInterval : 0 - focus: true + property int sensorState: sensor ? sensor.state : SensorTagData.Disconnected - Image { - id: ring - anchors.centerIn: parent - source: pathPrefix + "Gyro/gyro_outer.png" + onSensorStateChanged: { + if (sensorState === SensorTagData.Scanning) + sensorIcon.startBlinking(); + else + sensorIcon.stopBlinking(); } - Image { - id: outerRing - anchors.centerIn: parent - source: pathPrefix + "Gyro/gyro_ring3.png" - rotation: sensor ? sensor.rotationX : 0 - Behavior on rotation { - RotationAnimator { - easing.type: Easing.Linear - duration: rotationUpdateInterval - direction: RotationAnimator.Shortest + + Item { + anchors.fill: parent + + visible: sensorState === SensorTagData.Connected + + Image { + id: ring + anchors.centerIn: parent + source: pathPrefix + "Gyro/gyro_outer.png" + } + Image { + id: outerRing + anchors.centerIn: parent + source: pathPrefix + "Gyro/gyro_ring3.png" + rotation: sensor ? sensor.rotationX : 0 + Behavior on rotation { + RotationAnimator { + easing.type: Easing.Linear + duration: rotationUpdateInterval + direction: RotationAnimation.Shortest + } } } - } - Image { - id: largeRing - anchors.centerIn: parent - source: pathPrefix + "Gyro/gyro_ring2.png" - rotation: sensor ? sensor.rotationY : 0 - Behavior on rotation { - RotationAnimator { - easing.type: Easing.Linear - duration: rotationUpdateInterval - direction: RotationAnimator.Shortest + Image { + id: largeRing + anchors.centerIn: parent + source: pathPrefix + "Gyro/gyro_ring2.png" + rotation: sensor ? sensor.rotationY : 0 + Behavior on rotation { + RotationAnimator { + easing.type: Easing.Linear + duration: rotationUpdateInterval + direction: RotationAnimation.Shortest + } } } - } - Image { - id: mediumRing - anchors.centerIn: parent - source: pathPrefix + "Gyro/gyro_ring1.png" - rotation: sensor ? sensor.rotationZ : 0 - Behavior on rotation { - RotationAnimator { - easing.type: Easing.Linear - duration: rotationUpdateInterval - direction: RotationAnimator.Shortest + Image { + id: mediumRing + anchors.centerIn: parent + source: pathPrefix + "Gyro/gyro_ring1.png" + rotation: sensor ? sensor.rotationZ : 0 + Behavior on rotation { + RotationAnimator { + easing.type: Easing.Linear + duration: rotationUpdateInterval + direction: RotationAnimation.Shortest + } } } + Image { + id: centerRing + anchors.centerIn: parent + source: pathPrefix + "Gyro/gyro_center.png" + } } - Image { - id: centerRing - anchors.centerIn: parent - source: pathPrefix + "Gyro/gyro_center.png" + + Item { + id: connectingNote + + anchors.fill: parent + visible: sensorState === SensorTagData.Scanning || + sensorState === SensorTagData.Disconnected || + sensorState === SensorTagData.Error || + sensorState === SensorTagData.NotFound + + Column { + anchors.centerIn: parent + spacing: 20 + + BlinkingIcon { + id: sensorIcon + + source: pathPrefix + "Toolbar/icon_topbar_sensor.png" + anchors.horizontalCenter: parent.horizontalCenter + visible: sensorState === SensorTagData.Scanning + } + + Text { + id: stateText + + color: "white" + text: sensorState === SensorTagData.Scanning ? "Connecting to sensor..." + : sensorState === SensorTagData.Disconnected ? "Disconnected" + : sensorState === SensorTagData.NotFound ? "Device not found" + : "" + } + } } } diff --git a/tradeshow/iot-sensortag/resources/base/SensorSettings.qml b/tradeshow/iot-sensortag/resources/base/SensorSettings.qml index a2a8202..5aace2c 100644 --- a/tradeshow/iot-sensortag/resources/base/SensorSettings.qml +++ b/tradeshow/iot-sensortag/resources/base/SensorSettings.qml @@ -57,8 +57,8 @@ Rectangle { property alias listModelCount: list.count - width: 360 - height: 252 + width: 620 + height: 480 color: "black" Text { @@ -73,68 +73,129 @@ Rectangle { Image { id: icon - width: 60 - height: 60 + anchors.top: titleText.bottom anchors.horizontalCenter: parent.horizontalCenter anchors.margins: 8 source: pathPrefix + "Toolbar/icon_topbar_sensor.png" - - MouseArea { - anchors.fill: parent - onClicked: dataProviderPool.startScanning() - } } ListView { id: list anchors.top: icon.bottom anchors.topMargin: 16 - height: 150 - width: parent.width - clip: true + anchors.left: parent.left + anchors.leftMargin: 30 + anchors.right: parent.right + anchors.rightMargin: 30 orientation: ListView.Horizontal model: dataProviderPool.dataProviders + height: parent.height + clip: true + snapMode: ListView.SnapToItem + boundsBehavior: Flickable.StopAtBounds + + function getTagTypeStr(tagType) { + var tagStr = ""; + if (tagType & SensorTagData.AmbientTemperature) + tagStr += "Ambient Temperature\n"; + if (tagType & SensorTagData.ObjectTemperature) + tagStr += "Object Temperature\n"; + if (tagType & SensorTagData.Humidity) + tagStr += "Humidity\n"; + if (tagType & SensorTagData.Altitude) + tagStr += "Altitude\n"; + if (tagType & SensorTagData.Light) + tagStr += "Light\n"; + if (tagType & SensorTagData.Rotation) + tagStr += "Gyroscope\n"; + if (tagType & SensorTagData.Magnetometer) + tagStr += "Magnetometer\n"; + if (tagType & SensorTagData.Accelometer) + tagStr += "Accelometer\n"; + + return tagStr; + } delegate: Item { id: listItem width: mainRect.width / 3 + height: childrenRect.height + ColumnLayout { spacing: 8 - Item { - width: listItem.width - height: 40 - Text { - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.right - anchors.margins: 4 - horizontalAlignment: Text.AlignHCenter - text: providerId.toUpperCase() - color: "white" - font.pixelSize: 16 - elide: Text.ElideMiddle - clip: true - } + + Text { + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + horizontalAlignment: Text.AlignHCenter + text: providerId + color: "white" + font.pixelSize: 16 + elide: Text.ElideMiddle } - Image { - Layout.alignment: Qt.AlignHCenter - width: 40 - height: 40 + + BlinkingIcon { + id: sensorIcon + + property bool canBlink: modelData.state === SensorTagData.Scanning + + onCanBlinkChanged: { + if (canBlink) + sensorIcon.startBlinking(); + else + sensorIcon.stopBlinking(); + } + source: pathPrefix + "Toolbar/icon_topbar_sensor.png" - opacity: (modelData.state === SensorTagData.Connected - || modelData.state === SensorTagData.Scanning) ? 1 : 0.5 + anchors.horizontalCenter: parent.horizontalCenter + + Component.onDestruction: { + sensorIcon.stopBlinking() + } + + MouseArea { + anchors.fill: parent + onClicked: { + if (modelData.state === SensorTagData.Connected) + dataProviderPool.disconnectProvider(modelData.providerId); + else if (modelData.state === SensorTagData.NotFound) + dataProviderPool.startScanning(); + else if (modelData.state === SensorTagData.Scanning) + dataProviderPool.disconnectProvider(modelData.providerId) + else + modelData.startServiceScan(); + } + } } Text { Layout.alignment: Qt.AlignHCenter - text: modelData.state === SensorTagData.Disconnected ? "Disconnected" - : (modelData.state === SensorTagData.Scanning) ? "Scanning" - : (modelData.state === SensorTagData.Connected) ? "Connected" + text: modelData.state === SensorTagData.NotFound ? "\nNOT FOUND" + : (modelData.state === SensorTagData.Disconnected) ? "\nDISCONNECTED" + : (modelData.state === SensorTagData.Scanning) ? "\nCONNECTING" + : (modelData.state === SensorTagData.Connected) ? "\nCONNECTED" : "Error" color: "white" font.pixelSize: 14 } + + Item { + height: 30 + width: 10 + } + + Text { + color: "white" + text: "Provides data for:" + } + + Text { + color: "white" + lineHeight: 0.7 + text: list.getTagTypeStr(modelData.tagType()) + } } } } diff --git a/tradeshow/iot-sensortag/resources/base/main.qml b/tradeshow/iot-sensortag/resources/base/main.qml index c55d8ee..2840433 100644 --- a/tradeshow/iot-sensortag/resources/base/main.qml +++ b/tradeshow/iot-sensortag/resources/base/main.qml @@ -58,6 +58,7 @@ Window { property alias contentFile: contentLoader.source property DataProviderPool dataProviderPool property SeriesStorage seriesStorage + property real globalBlinkOpacity: 1.0 // Size defaults to the small display width: 1920 @@ -76,4 +77,40 @@ Window { anchors.fill: parent anchors.centerIn: parent } + + + + function startBlink() { + flash.blinkers++; + } + + function stopBlink() { + flash.blinkers--; + } + + // Animation to allow synchronized + // blinking of BlinkingIcons + SequentialAnimation { + id: flash + + property int blinkers: 0 + + running: blinkers + loops: Animation.Infinite + alwaysRunToEnd: true + + PropertyAnimation { + target: mainWindow + property: "globalBlinkOpacity" + to: 0.3 + duration: 700 + } + + PropertyAnimation { + target: mainWindow + property: "globalBlinkOpacity" + to: 1 + duration: 700 + } + } } diff --git a/tradeshow/iot-sensortag/resources/small/MainSmall.qml b/tradeshow/iot-sensortag/resources/small/MainSmall.qml index df4a868..83835e3 100644 --- a/tradeshow/iot-sensortag/resources/small/MainSmall.qml +++ b/tradeshow/iot-sensortag/resources/small/MainSmall.qml @@ -54,25 +54,31 @@ import "../base" Item { id: main + function startRescan(sensor) { + if (sensor.state === SensorTagData.NotFound) + dataProviderPool.startScanning(); + else if (sensor.state === SensorTagData.Scanning) + dataProviderPool.disconnectProvider(sensor.providerId) + else if (sensor.state !== SensorTagData.Connected) + sensor.startServiceScan(); + } + anchors.fill: parent Component.onCompleted: { - dataProviderPool.startScanning() - } - - Connections { - target: dataProviderPool - onScanFinished: { - ambientTemp.sensor = dataProviderPool.getProvider(SensorTagData.AmbientTemperature); - objectTemp.sensor = dataProviderPool.getProvider(SensorTagData.ObjectTemperature); - humidity.sensor = dataProviderPool.getProvider(SensorTagData.Humidity); - airPressure.sensor = dataProviderPool.getProvider(SensorTagData.AirPressure); - light.sensor = dataProviderPool.getProvider(SensorTagData.Light); - magnetometer.sensor = dataProviderPool.getProvider(SensorTagData.Magnetometer); - rotation.sensor = dataProviderPool.getProvider(SensorTagData.Rotation); - accelometer.sensor = dataProviderPool.getProvider(SensorTagData.Accelometer); - rotationMain.sensor = dataProviderPool.getProvider(SensorTagData.Rotation); - } + dataProviderPool.startScanning(); + + // UI gets information about the intended setup of the + // sensor even though they have not been really discovered yet + ambientTemp.sensor = dataProviderPool.getProvider(SensorTagData.AmbientTemperature); + objectTemp.sensor = dataProviderPool.getProvider(SensorTagData.ObjectTemperature); + humidity.sensor = dataProviderPool.getProvider(SensorTagData.Humidity); + airPressure.sensor = dataProviderPool.getProvider(SensorTagData.AirPressure); + light.sensor = dataProviderPool.getProvider(SensorTagData.Light); + magnetometer.sensor = dataProviderPool.getProvider(SensorTagData.Magnetometer); + rotation.sensor = dataProviderPool.getProvider(SensorTagData.Rotation); + accelometer.sensor = dataProviderPool.getProvider(SensorTagData.Accelometer); + rotationMain.sensor = dataProviderPool.getProvider(SensorTagData.Rotation); } Column { @@ -92,6 +98,7 @@ Item { width: leftPane.width height: leftPane.indicatorHeight + onClicked: main.startRescan(sensor) } ObjectTemperatureChart { @@ -99,6 +106,7 @@ Item { width: leftPane.width height: leftPane.indicatorHeight + onClicked: main.startRescan(sensor) } HumidityChart { @@ -106,6 +114,7 @@ Item { width: leftPane.width height: leftPane.indicatorHeight + onClicked: main.startRescan(sensor) } AltitudeChart { @@ -113,6 +122,7 @@ Item { width: leftPane.width height: leftPane.indicatorHeight + onClicked: main.startRescan(sensor) } } @@ -131,6 +141,7 @@ Item { width: rightPane.width height: rightPane.height / 4 + onClicked: main.startRescan(sensor) } MagnetometerChart { @@ -138,6 +149,7 @@ Item { width: rightPane.width height: rightPane.height * 0.3 - 30 + onClicked: main.startRescan(sensor) } GyroChart { @@ -145,6 +157,7 @@ Item { width: rightPane.width height: rightPane.height * 0.3 - 60 + onClicked: main.startRescan(sensor) } AccelChart { @@ -152,6 +165,7 @@ Item { width: rightPane.width height: rightPane.height - light.height - magnetometer.height - rotation.height - 3 * rightPane.spacing + onClicked: main.startRescan(sensor) } } @@ -167,7 +181,6 @@ Item { onSensorChanged: if (sensor) sensor.recalibrate() } - TopToolbar { id: topToolbar diff --git a/tradeshow/iot-sensortag/sensortagdataprovider.cpp b/tradeshow/iot-sensortag/sensortagdataprovider.cpp index 4ca749c..f5defa3 100644 --- a/tradeshow/iot-sensortag/sensortagdataprovider.cpp +++ b/tradeshow/iot-sensortag/sensortagdataprovider.cpp @@ -101,7 +101,7 @@ SensorTagDataProvider::SensorTagDataProvider(QString id, QObject* parent) altitude(0), m_tagType(AmbientTemperature | ObjectTemperature | Humidity | AirPressure | Light | Magnetometer | Rotation | Accelometer | Altitude), m_id(id), - m_state(Disconnected) + m_state(NotFound) { } diff --git a/tradeshow/iot-sensortag/sensortagdataprovider.h b/tradeshow/iot-sensortag/sensortagdataprovider.h index 3f55386..605c5d0 100644 --- a/tradeshow/iot-sensortag/sensortagdataprovider.h +++ b/tradeshow/iot-sensortag/sensortagdataprovider.h @@ -98,7 +98,7 @@ public: Rotation = 1 << 6, Accelometer = 1 << 7, Altitude = 1 << 8}; - enum ProviderState {Disconnected = 0, Scanning, Connected, Error}; + enum ProviderState {NotFound = 0, Disconnected, Scanning, Connected, Error}; explicit SensorTagDataProvider(QObject *parent = 0); SensorTagDataProvider(QString id, QObject *parent = 0); diff --git a/tradeshow/iot-sensortag/sensortagdataproviderpool.cpp b/tradeshow/iot-sensortag/sensortagdataproviderpool.cpp index f9edad0..987e8b3 100644 --- a/tradeshow/iot-sensortag/sensortagdataproviderpool.cpp +++ b/tradeshow/iot-sensortag/sensortagdataproviderpool.cpp @@ -74,16 +74,22 @@ SensorTagDataProviderPool::SensorTagDataProviderPool(QString poolName, QObject* void SensorTagDataProviderPool::startScanning() { - qDeleteAll(m_dataProviders); - m_dataProviders.clear(); - - m_discoveryAgent->start(); + m_discoveryAgent->start(QBluetoothDeviceDiscoveryAgent::LowEnergyMethod); if (m_discoveryAgent->isActive()) { - m_deviceScanState = true; + emit scanStarted(); } } +void SensorTagDataProviderPool::disconnectProvider(QString id) +{ + SensorTagDataProvider *p = findProvider(id); + if (BluetoothDataProvider *btp = qobject_cast(findProvider(id))) + btp->unbindDevice(); + else if (p) + p->setState(SensorTagDataProvider::Disconnected); +} + void SensorTagDataProviderPool::setMacFilterList(const QStringList &addressList) { m_macFilters = addressList; @@ -118,7 +124,6 @@ void SensorTagDataProviderPool::updateProviderForCloud() void SensorTagDataProviderPool::deviceDiscoveryFinished() { - m_deviceScanState = false; finishScanning(); emit scanFinished(); } @@ -130,8 +135,6 @@ void SensorTagDataProviderPool::finishScanning() void SensorTagDataProviderPool::btDeviceFound(const QBluetoothDeviceInfo &info) { - qCDebug(boot2QtDemos) << "Found a Bluetooth device. Name:" << info.name() << ", addr:" << info.address().toString(); - if (info.coreConfigurations() & QBluetoothDeviceInfo::LowEnergyCoreConfiguration) { bool filtered = m_macFilters.length() || m_nameFilters.length(); bool found = filtered ? false : true; @@ -142,14 +145,24 @@ void SensorTagDataProviderPool::btDeviceFound(const QBluetoothDeviceInfo &info) found = true; if (found) { - qCDebug(boot2QtDemos) << " Adding to the available devices"; - BluetoothDataProvider* dataProvider = new BluetoothDataProvider(info.address().toString(), this); - BluetoothDevice *d = new BluetoothDevice(info); - dataProvider->bindToDevice(d); - m_dataProviders.append(dataProvider); - emit providerConnected(d->getAddress()); - emit dataProvidersChanged(); - connect(dataProvider, &SensorTagDataProvider::stateChanged, this, &SensorTagDataProviderPool::handleStateChange); + BluetoothDataProvider *dataProvider = static_cast(findProvider(info.address().toString())); + if (!dataProvider) { + qCDebug(boot2QtDemos) << "Found a new Sensor tag. Name:" << info.name() << ", addr:" << info.address().toString() ; + dataProvider = new BluetoothDataProvider(info.address().toString(), this); + m_dataProviders.append(dataProvider); + emit dataProvidersChanged(); + } + if (!dataProvider->device()) { + qCDebug(boot2QtDemos) << "Attach BluetoothDevice info for an existing Sensor Tag:" << info.name(); + BluetoothDevice *d = new BluetoothDevice(info); + dataProvider->bindToDevice(d); + connect(dataProvider, &SensorTagDataProvider::stateChanged, this, &SensorTagDataProviderPool::handleStateChange); + dataProvider->startDataFetching(); + } + else if (dataProvider->state() != SensorTagDataProvider::Connected) { + qCDebug(boot2QtDemos) << "Start service scan for already attached Sensor Tag" << dataProvider->id(); + dataProvider->startServiceScan(); + } } } } @@ -184,6 +197,14 @@ void SensorTagDataProviderPool::deviceScanError(QBluetoothDeviceDiscoveryAgent:: else qCDebug(boot2QtDemos) << "An unknown error has occurred."; - m_deviceScanState = false; emit scanFinished(); } + +SensorTagDataProvider* SensorTagDataProviderPool::findProvider(QString id) const +{ + for (SensorTagDataProvider *p : m_dataProviders) { + if (id == p->id()) + return p; + } + return 0; +} diff --git a/tradeshow/iot-sensortag/sensortagdataproviderpool.h b/tradeshow/iot-sensortag/sensortagdataproviderpool.h index d7ac740..fe8b30f 100644 --- a/tradeshow/iot-sensortag/sensortagdataproviderpool.h +++ b/tradeshow/iot-sensortag/sensortagdataproviderpool.h @@ -66,6 +66,7 @@ public: explicit SensorTagDataProviderPool(QObject *parent = 0); void startScanning() override; + void disconnectProvider(QString id) override; // setMacFilterList takes presence over name filter Q_INVOKABLE void setMacFilterList(const QStringList& addressList); @@ -77,9 +78,11 @@ public: protected: SensorTagDataProviderPool(QString poolName, QObject *parent = 0); virtual void finishScanning(); - void updateProviderForCloud(); + QStringList m_macFilters; + QStringList m_nameFilters; + private slots: void deviceDiscoveryFinished(); void btDeviceFound(const QBluetoothDeviceInfo &info); @@ -87,9 +90,8 @@ private slots: void deviceScanError(QBluetoothDeviceDiscoveryAgent::Error error); private: - QStringList m_macFilters; - QStringList m_nameFilters; - bool m_deviceScanState; + SensorTagDataProvider *findProvider(QString id) const; + QBluetoothDeviceDiscoveryAgent *m_discoveryAgent; SensorTagDataProvider *m_providerForCloud; }; -- cgit v1.2.3