From bb226a8dc9b6508c8d72c67b5260b9a733f7fca1 Mon Sep 17 00:00:00 2001 From: Maurice Kalinowski Date: Wed, 20 Sep 2017 09:37:05 +0200 Subject: iot-sensortag: Add support for MQTT Using MQTT a sensor can send its data to a broker as well as display data from a remote MQTT broker. Change-Id: I01f961e89b2c6d028498ce61e4087a47742b8b82 Reviewed-by: Maurice Kalinowski --- tradeshow/iot-sensortag/SensorTagDemo.pro | 77 +++-- tradeshow/iot-sensortag/main.cpp | 29 +- tradeshow/iot-sensortag/mqttdataprovider.cpp | 214 +++++++++++++ tradeshow/iot-sensortag/mqttdataproviderpool.cpp | 123 ++++++++ tradeshow/iot-sensortag/mqttdataproviderpool.h | 5 + tradeshow/iot-sensortag/mqttupdate.cpp | 371 +++++++++++++++++++++++ tradeshow/iot-sensortag/mqttupdate.h | 121 ++++++++ 7 files changed, 891 insertions(+), 49 deletions(-) create mode 100644 tradeshow/iot-sensortag/mqttdataprovider.cpp create mode 100644 tradeshow/iot-sensortag/mqttdataproviderpool.cpp create mode 100644 tradeshow/iot-sensortag/mqttupdate.cpp create mode 100644 tradeshow/iot-sensortag/mqttupdate.h (limited to 'tradeshow/iot-sensortag') diff --git a/tradeshow/iot-sensortag/SensorTagDemo.pro b/tradeshow/iot-sensortag/SensorTagDemo.pro index bd79351..c69a181 100644 --- a/tradeshow/iot-sensortag/SensorTagDemo.pro +++ b/tradeshow/iot-sensortag/SensorTagDemo.pro @@ -1,7 +1,15 @@ TEMPLATE = app -QT += 3dcore 3drender 3dinput 3dquick 3dlogic core gui qml quick 3dquickextras widgets -QT += bluetooth network charts +QT += \ + bluetooth \ + core \ + charts \ + gui \ + network \ + qml \ + quick \ + widgets + CONFIG += c++11 DEFINES += QT_NO_FOREACH @@ -9,25 +17,17 @@ DEFINES += QT_NO_FOREACH # Needed at least for RPi3 and iMX #CONFIG += DEPLOY_TO_FS -# Uncomment DEVICE_TYPE and assign either UI_SMALL, UI_MEDIUM, UI_LARGE -# to force using that UI form factor. Otherwise -# the form factor is determined based on the platform -DEVICE_TYPE = UI_SMALL - -# If DEVICE_TYPE is not set manually, try to determine -# the correct device type by the used operating system -win32|linux:!android:!qnx { +win32|linux|android:!qnx { CONFIG += BLUETOOTH_HOST - isEmpty(DEVICE_TYPE) { DEVICE_TYPE = UI_SMALL } -} else:android { - isEmpty(DEVICE_TYPE) { DEVICE_TYPE = UI_MEDIUM } - ANDROID_PACKAGE_SOURCE_DIR = $$PWD/android-sources - QMAKE_CXX_FLAGS -= -DQT_OPENGL_FORCE_SHADER_DEFINES -} else:ios { - isEmpty(DEVICE_TYPE) { DEVICE_TYPE = UI_MEDIUM } +} else { + message(Unsupported target platform) } -win32 { +# For using MQTT upload enable this config. +# This enables both, host and client mode +CONFIG += UPDATE_TO_MQTT_BROKER + +win32:!contains(CONFIG, UPDATE_TO_MQTT_BROKER) { WASTORAGE_PATH = $$(WASTORAGE_LOCATION) isEmpty(WASTORAGE_PATH): message("Location for Azure Storage libs unknown. Please specify WASTORAGE_LOCATION") CPPRESTSDK_PATH = $$(CPPRESTSDK_LOCATION) @@ -83,6 +83,21 @@ BLUETOOTH_HOST { bluetoothdevice.h } +UPDATE_TO_MQTT_BROKER { + CONFIG -= UPDATE_TO_AZURE + + !qtHaveModule(mqtt): error("Could not find MQTT module for Qt version") + QT += mqtt + DEFINES += MQTT_UPLOAD + + SOURCES += mqttupdate.cpp \ + mqttdataproviderpool.cpp \ + mqttdataprovider.cpp + HEADERS += mqttupdate.h \ + mqttdataproviderpool.h \ + mqttdataprovider.h +} + UPDATE_TO_AZURE { SOURCES += cloudupdate.cpp HEADERS += cloudupdate.h @@ -100,29 +115,9 @@ UPDATE_TO_AZURE { RESOURCES += base.qrc -equals(DEVICE_TYPE, "UI_SMALL") { - DEFINES += UI_SMALL - !DEPLOY_TO_FS: RESOURCES += uismall.qrc - uiVariant.files = resources/small - uiVariant.path = /opt/$${TARGET}/resources - message("Resource file for SMALL display picked") -} - -equals(DEVICE_TYPE, "UI_MEDIUM") { - DEFINES += UI_MEDIUM - !DEPLOY_TO_FS: RESOURCES += uimedium.qrc - uiVariant.files = resources/medium - uiVariant.path = /opt/$${TARGET}/resources - message("Resource file for MEDIUM display picked") -} - -equals(DEVICE_TYPE, "UI_LARGE") { - DEFINES += UI_LARGE - !DEPLOY_TO_FS: RESOURCES += uilarge.qrc - uiVariant.files = resources/large - uiVariant.path = /opt/$${TARGET}/resources - message("Resource file for LARGE display picked") -} +!DEPLOY_TO_FS: RESOURCES += uismall.qrc +uiVariant.files = resources/small +uiVariant.path = /opt/$${TARGET}/resources # Additional import path used to resolve QML modules in Qt Creator's code model QML_IMPORT_PATH = diff --git a/tradeshow/iot-sensortag/main.cpp b/tradeshow/iot-sensortag/main.cpp index 30c8813..555d1a8 100644 --- a/tradeshow/iot-sensortag/main.cpp +++ b/tradeshow/iot-sensortag/main.cpp @@ -47,13 +47,6 @@ ** $QT_END_LICENSE$ ** ****************************************************************************/ -#include -#include -#include -#include -#include -#include -#include #if defined(RUNS_AS_HOST) #include "bluetoothdataprovider.h" @@ -67,9 +60,21 @@ #include "mockdataproviderpool.h" #ifdef AZURE_UPLOAD #include "cloudupdate.h" +#elif defined (MQTT_UPLOAD) +#include "mqttupdate.h" +#include "mqttdataprovider.h" +#include "mqttdataproviderpool.h" #endif #include "seriesstorage.h" +#include +#include +#include +#include +#include +#include +#include + Q_DECLARE_LOGGING_CATEGORY(boot2QtDemos) Q_LOGGING_CATEGORY(boot2QtDemos, "boot2qt.demos.iot") @@ -90,6 +95,9 @@ int main(int argc, char *argv[]) parser.addHelpOption(); parser.process(app); +#if defined(MQTT_UPLOAD) + remoteProviderPool = new MqttDataProviderPool; +#endif #if defined(RUNS_AS_HOST) // localProviderPool = new MockDataProviderPool; localProviderPool = new DemoDataProviderPool; @@ -100,8 +108,13 @@ int main(int argc, char *argv[]) qmlRegisterType("SensorTag.DataProvider", 1, 0, "DataProviderPool"); qmlRegisterType("SensorTag.SeriesStorage", 1, 0, "SeriesStorage"); -#if defined(RUNS_AS_HOST) && defined(AZURE_UPLOAD) +#if defined(RUNS_AS_HOST) && (defined(AZURE_UPLOAD) || defined(MQTT_UPLOAD)) +#if AZURE_UPLOAD CloudUpdate update; +# else + MqttUpdate update; +# endif + update.setDataProviderPool(localProviderPool); update.restart(); #endif diff --git a/tradeshow/iot-sensortag/mqttdataprovider.cpp b/tradeshow/iot-sensortag/mqttdataprovider.cpp new file mode 100644 index 0000000..8985f02 --- /dev/null +++ b/tradeshow/iot-sensortag/mqttdataprovider.cpp @@ -0,0 +1,214 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#include "mqttdataprovider.h" + +#include +#include +#include + +#define MAJOR_VERSION_NUMBER 1 +#define MINOR_VERSION_NUMBER 0 + +Q_DECLARE_LOGGING_CATEGORY(boot2QtDemos) + +MqttDataProvider::MqttDataProvider(QString id, QMqttClient *client, QObject *parent) + : SensorTagDataProvider(id, parent) + , m_client(client) + , m_subscription(nullptr) +{ + intervalRotation = 200; + + m_pollTimer = new QTimer(this); + m_pollTimer->setInterval(intervalRotation); + m_pollTimer->setSingleShot(false); + connect(m_pollTimer, &QTimer::timeout, this, &MqttDataProvider::dataTimeout); +} + +bool MqttDataProvider::startDataFetching() +{ + const QString subName = QString::fromLocal8Bit("sensors/%1/#").arg(m_id); + + m_subscription = m_client->subscribe(subName); + connect(m_subscription, &QMqttSubscription::messageReceived, + this, &MqttDataProvider::messageReceived); + return true; +} + +void MqttDataProvider::endDataFetching() +{ + if (m_subscription) { + disconnect(m_subscription, &QMqttSubscription::messageReceived, + this, &MqttDataProvider::messageReceived); + m_subscription->unsubscribe(); + m_subscription = nullptr; + } +} + +QString MqttDataProvider::sensorType() const +{ + return QString("mqtt data"); +} + +QString MqttDataProvider::versionString() const +{ + return QString::number(MAJOR_VERSION_NUMBER) + "." + QString::number(MINOR_VERSION_NUMBER); +} + +void MqttDataProvider::reset() +{ +} + +void MqttDataProvider::messageReceived(const QMqttMessage &msg) +{ + parseMessage(msg.payload(), msg.topic()); + if (!m_pollTimer->isActive()) + m_pollTimer->start(); +} + +void MqttDataProvider::parseMessage(const QString &content, const QString &topic) +{ + const QString msgType = topic.split(QLatin1Char('/')).last(); + if (msgType == QStringLiteral("type")) { + qDebug() << "Type: " << content; + } else if (msgType == QStringLiteral("version")) { + qDebug() << "Version: " << content; + } else if (msgType == QStringLiteral("humid")) { + bool ok; + const double v = content.toDouble(&ok); + if (ok) + humidity = v; + } else if (msgType == QStringLiteral("light")) { + bool ok; + const double v = content.toDouble(&ok); + if (ok) + lightIntensityLux = v; + } else if (msgType == QStringLiteral("temperature")) { + //ambient_object + QString streamContent = content; + QTextStream stream(&streamContent); + double ambient, object; + char c; + stream >> ambient >> c >> object; + + irAmbientTemperature = ambient; + irObjectTemperature = object; + } else if (msgType == QStringLiteral("barometer")) { + QString streamContent = content; + QTextStream stream(&streamContent); + double v1, v2; + char c; + stream >> v1 >> c >> v2; + barometerCelsiusTemperature = v1; + barometerHPa = v2; + } else if (msgType == QStringLiteral("gyro")) { + QString streamContent = content; + QTextStream stream(&streamContent); + double v1, v2, v3; + char c; + stream >> v1 >> c >> v2 >> c >> v3; + gyroscopeX_degPerSec = v1; + gyroscopeY_degPerSec = v2; + gyroscopeZ_degPerSec = v3; + } else if (msgType == QStringLiteral("accel")) { + QString streamContent = content; + QTextStream stream(&streamContent); + double v1, v2, v3; + char c; + stream >> v1 >> c >> v2 >> c >> v3; + accelometerX = v1; + accelometerY = v2; + accelometerZ = v3; + } else if (msgType == QStringLiteral("magnet")) { + QString streamContent = content; + QTextStream stream(&streamContent); + double v1, v2, v3; + char c; + stream >> v1 >> c >> v2 >> c >> v3; + magnetometerMicroT_xAxis = v1; + magnetometerMicroT_yAxis = v2; + magnetometerMicroT_zAxis = v3; + } else if (msgType == QStringLiteral("rotation")) { + QString streamContent = content; + QTextStream stream(&streamContent); + double v1, v2, v3; + char c; + stream >> v1 >> c >> v2 >> c >> v3; + rotation_x = v1; + rotation_y = v2; + rotation_z = v3; + } else if (msgType == QStringLiteral("altitude")) { + QString streamContent = content; + QTextStream stream(&streamContent); + float alt; + stream >> alt; + altitude = alt; + } else { + qWarning() << "Unknown sensor data received:" << topic; + } +} + +void MqttDataProvider::dataTimeout() +{ + emit relativeHumidityChanged(); + emit lightIntensityChanged(); + emit infraredAmbientTemperatureChanged(); + emit infraredObjectTemperatureChanged(); + emit barometerCelsiusTemperatureChanged(); + emit barometer_hPaChanged(); + emit gyroscopeDegPerSecChanged(); + emit accelometerChanged(); + emit magnetometerMicroTChanged(); + emit rotationXChanged(); + emit rotationYChanged(); + emit rotationZChanged(); + emit altitudeChanged(); + emit rotationValuesChanged(); +} diff --git a/tradeshow/iot-sensortag/mqttdataproviderpool.cpp b/tradeshow/iot-sensortag/mqttdataproviderpool.cpp new file mode 100644 index 0000000..24ce47c --- /dev/null +++ b/tradeshow/iot-sensortag/mqttdataproviderpool.cpp @@ -0,0 +1,123 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#include "mqttdataproviderpool.h" +#include "mqttdataprovider.h" + +#include + +MqttDataProviderPool::MqttDataProviderPool(QObject *parent) + : DataProviderPool(parent) + , m_client(new QMqttClient(this)) +{ + m_poolName = "Mqtt"; +} + +void MqttDataProviderPool::startScanning() +{ + emit providerConnected("MQTT_CLOUD"); + emit providersUpdated(); + emit dataProvidersChanged(); + + m_client->setHostname(QLatin1String(MQTT_BROKER)); + m_client->setPort(MQTT_PORT); + m_client->setUsername(QByteArray(MQTT_USERNAME)); + m_client->setPassword(QByteArray(MQTT_PASSWORD)); + + connect(m_client, &QMqttClient::connected, [this]() { + auto sub = m_client->subscribe(QLatin1String("sensors/active")); + connect(sub, &QMqttSubscription::messageReceived, this, &MqttDataProviderPool::deviceUpdate); + }); + connect(m_client, &QMqttClient::disconnected, [this]() { + qDebug() << "Pool client disconnected"; + }); + m_client->connectToHost(); +} + +void MqttDataProviderPool::deviceUpdate(const QMqttMessage &msg) +{ + static QSet knownDevices; + // Registration is: deviceName>Online + const QByteArrayList payload = msg.payload().split('>'); + const QString deviceName = payload.first(); + const QString deviceStatus = payload.at(1); + const QString subName = QString::fromLocal8Bit("sensors/%1/#").arg(deviceName); + + bool updateRequired = false; + if (deviceStatus == QLatin1String("Online")) { // new device + // Skip local items + if (deviceName.startsWith(QSysInfo::machineHostName())) + return; + + if (!knownDevices.contains(deviceName)) { + auto prov = new MqttDataProvider(deviceName, m_client, this); + prov->setState(SensorTagDataProvider::Connected); + m_dataProviders.push_back(prov); + if (m_currentProvider == nullptr) + setCurrentProviderIndex(m_dataProviders.size() - 1); + knownDevices.insert(deviceName); + updateRequired = true; + } + } else if (deviceStatus == QLatin1String("Offline")) { // device died + knownDevices.remove(deviceName); + updateRequired = true; + for (auto prov : m_dataProviders) { + if (prov->id() == deviceName) { + m_dataProviders.removeAll(prov); + break; + } + } + } + + if (updateRequired) { + emit providersUpdated(); + emit dataProvidersChanged(); + } +} diff --git a/tradeshow/iot-sensortag/mqttdataproviderpool.h b/tradeshow/iot-sensortag/mqttdataproviderpool.h index bcd29c6..1ba33ff 100644 --- a/tradeshow/iot-sensortag/mqttdataproviderpool.h +++ b/tradeshow/iot-sensortag/mqttdataproviderpool.h @@ -55,6 +55,11 @@ class MqttDataProvider; +#define MQTT_BROKER "" +#define MQTT_PORT 1883 +#define MQTT_USERNAME "" +#define MQTT_PASSWORD "" + class MqttDataProviderPool : public DataProviderPool { public: diff --git a/tradeshow/iot-sensortag/mqttupdate.cpp b/tradeshow/iot-sensortag/mqttupdate.cpp new file mode 100644 index 0000000..f574965 --- /dev/null +++ b/tradeshow/iot-sensortag/mqttupdate.cpp @@ -0,0 +1,371 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#include "mqttupdate.h" +#include "cloudservice.h" +#include "dataproviderpool.h" +#include "sensortagdataprovider.h" +#include "demodataproviderpool.h" +#include "mqttdataproviderpool.h" + +#include +#include + +#define ROUNDING_DECIMALS 2 +#define UPDATE_INTERVAL 100 + +Q_DECLARE_LOGGING_CATEGORY(boot2QtDemos) + +MqttUpdate::MqttUpdate(QObject *parent) + : QObject(parent) + , m_providerPool(0) +#ifdef MQTT_TIMER_BASED_PUBLISH + , m_timerId(0) +#endif + , m_handler(nullptr) + , m_updateInterval(UPDATE_INTERVAL) +{ +} + +void MqttUpdate::setDataProviderPool(DataProviderPool *provider) +{ + m_providerPool = provider; + connect(m_providerPool, &DataProviderPool::currentProviderChanged, this, [=]() { + if (m_handler) + m_handler->deleteLater(); + + SensorTagDataProvider *provider = m_providerPool->currentProvider(); + if (!provider) + return; + + const QString hostname = QSysInfo::machineHostName(); + + m_handler = new MqttEventHandler(hostname + QLatin1String(" - ") + provider->id()); + m_handler->m_providerPool = m_providerPool; + + connect(provider, &SensorTagDataProvider::gyroscopeDegPerSecChanged, m_handler, &MqttEventHandler::uploadGyro); + connect(provider, &SensorTagDataProvider::accelometerChanged, m_handler, &MqttEventHandler::uploadAccelerometer); + connect(provider, &SensorTagDataProvider::rotationXChanged, m_handler, &MqttEventHandler::uploadRotation); + connect(provider, &SensorTagDataProvider::infraredAmbientTemperatureChanged, m_handler, &MqttEventHandler::uploadTemperature); + connect(provider, &SensorTagDataProvider::magnetometerMicroTChanged, m_handler, &MqttEventHandler::uploadMagnetometer); + connect(provider, &SensorTagDataProvider::relativeHumidityChanged, m_handler, &MqttEventHandler::uploadHumidity); + connect(provider, &SensorTagDataProvider::lightIntensityChanged, m_handler, &MqttEventHandler::uploadLight); + connect(provider, &SensorTagDataProvider::barometer_hPaChanged, m_handler, &MqttEventHandler::uploadBarometer); + connect(provider, &SensorTagDataProvider::altitudeChanged, m_handler, &MqttEventHandler::uploadAltitude); + }); +} + +int MqttUpdate::updateInterval() const +{ + return m_updateInterval; +} + +void MqttUpdate::setUpdateInterval(int interval) +{ + m_updateInterval = interval; +} + +void MqttUpdate::restart() +{ +#ifdef MQTT_TIMER_BASED_PUBLISH + killTimer(m_timerId); + m_timerId = startTimer(m_updateInterval); +#endif +} + +void MqttUpdate::stop() +{ +#ifdef MQTT_TIMER_BASED_PUBLISH + killTimer(m_timerId); +#endif +} + +#ifndef MQTT_TIMER_BASED_PUBLISH + +inline bool ensureConnected(QMqttClient *client) +{ + if (client->state() == QMqttClient::Connected) + return true; + + if (client->state() == QMqttClient::Disconnected) { + qCDebug(boot2QtDemos) << "Disconnected, need to reconnect"; + client->connectToHost(); + } + + return false; // Connecting, nothing to do +} + +void MqttEventHandler::uploadGyro() +{ + if (!ensureConnected(m_client)) + return; + SensorTagDataProvider *provider = m_providerPool->currentProvider(); + + if (provider) { + const QString gyro = QString("%1_%2_%3").arg(provider->getGyroscopeX_degPerSec(), 0, 'f', ROUNDING_DECIMALS) + .arg(provider->getGyroscopeY_degPerSec(), 0, 'f', ROUNDING_DECIMALS) + .arg(provider->getGyroscopeZ_degPerSec(), 0, 'f', ROUNDING_DECIMALS); + m_client->publish(m_topicPrefix + QLatin1String("gyro"), gyro.toLocal8Bit()); + } +} + +void MqttEventHandler::uploadTemperature() +{ + if (!ensureConnected(m_client)) + return; + + SensorTagDataProvider *provider = m_providerPool->currentProvider(); + + if (provider) { + const QString temperature = QString("%1_%2").arg(provider->getInfraredAmbientTemperature(), 0, 'f', ROUNDING_DECIMALS) + .arg(provider->getInfraredObjectTemperature(), 0, 'f', ROUNDING_DECIMALS); + m_client->publish(m_topicPrefix + QLatin1String("temperature"), temperature.toLocal8Bit()); + } +} + +void MqttEventHandler::uploadHumidity() +{ + if (!ensureConnected(m_client)) + return; + + SensorTagDataProvider *provider = m_providerPool->currentProvider(); + + if (provider) + m_client->publish(m_topicPrefix + QLatin1String("humid"), + QString("%1").arg(provider->getRelativeHumidity(), 0, 'f', ROUNDING_DECIMALS).toLocal8Bit()); +} + +void MqttEventHandler::uploadLight() +{ + if (!ensureConnected(m_client)) + return; + + SensorTagDataProvider *provider = m_providerPool->currentProvider(); + + if (provider) + m_client->publish(m_topicPrefix + QLatin1String("light"), + QString("%1").arg(provider->getLightIntensityLux(), 0, 'f', ROUNDING_DECIMALS).toLocal8Bit()); +} + +void MqttEventHandler::uploadAltitude() +{ + if (!ensureConnected(m_client)) + return; + + SensorTagDataProvider *provider = m_providerPool->currentProvider(); + + if (provider) + m_client->publish(m_topicPrefix + QLatin1String("altitude"), + QString("%1").arg(provider->getAltitude(), 0, 'f', ROUNDING_DECIMALS).toLocal8Bit()); +} + +void MqttEventHandler::uploadBarometer() +{ + if (!ensureConnected(m_client)) + return; + + SensorTagDataProvider *provider = m_providerPool->currentProvider(); + + if (provider) { + const QString baro = QString("%1_%2").arg(provider->getBarometerCelsiusTemperature(), 0, 'f', ROUNDING_DECIMALS) + .arg(provider->getBarometer_hPa(), 0, 'f', ROUNDING_DECIMALS); + m_client->publish(m_topicPrefix + QLatin1String("barometer"), baro.toLocal8Bit()); + } +} + +void MqttEventHandler::uploadAccelerometer() +{ + if (!ensureConnected(m_client)) + return; + + SensorTagDataProvider *provider = m_providerPool->currentProvider(); + + if (provider) { + const QString accel = QString("%1_%2_%3").arg(provider->getAccelometer_xAxis(), 0, 'f', ROUNDING_DECIMALS) + .arg(provider->getAccelometer_yAxis(), 0, 'f', ROUNDING_DECIMALS) + .arg(provider->getAccelometer_zAxis(), 0, 'f', ROUNDING_DECIMALS); + m_client->publish(m_topicPrefix + QLatin1String("accel"), accel.toLocal8Bit()); + } +} + +void MqttEventHandler::uploadMagnetometer() +{ + if (!ensureConnected(m_client)) + return; + + SensorTagDataProvider *provider = m_providerPool->currentProvider(); + + if (provider) { + const QString magnet = QString("%1_%2_%3").arg(provider->getMagnetometerMicroT_xAxis(), 0, 'f', ROUNDING_DECIMALS) + .arg(provider->getMagnetometerMicroT_yAxis(), 0, 'f', ROUNDING_DECIMALS) + .arg(provider->getMagnetometerMicroT_zAxis(), 0, 'f', ROUNDING_DECIMALS); + m_client->publish(m_topicPrefix + QLatin1String("magnet"), magnet.toLocal8Bit()); + } +} + +void MqttEventHandler::uploadRotation() +{ + if (!ensureConnected(m_client)) + return; + + SensorTagDataProvider *provider = m_providerPool->currentProvider(); + + if (provider) { + const QString rotation = QString("%1_%2_%3").arg(provider->getRotationX(), 0, 'f', ROUNDING_DECIMALS) + .arg(provider->getRotationY(), 0, 'f', ROUNDING_DECIMALS) + .arg(provider->getRotationZ(), 0, 'f', ROUNDING_DECIMALS); + m_client->publish(m_topicPrefix + QLatin1String("rotation"), rotation.toLocal8Bit()); + } +} + +void MqttEventHandler::clientConnected() +{ + m_pingTimer.start(); +} + +void MqttEventHandler::sendAlive() +{ + m_client->publish(QLatin1String("sensors/active"), QString::fromLocal8Bit("%1>Online").arg(m_deviceName).toLocal8Bit(), 1); +} +#endif // !MQTT_TIMER_BASED_PUBLISH + +#ifdef MQTT_TIMER_BASED_PUBLISH +void MqttUpdate::timerEvent(QTimerEvent *event) +{ + Q_UNUSED(event); + + if (!m_provider) + return; + + writeToCloud(); +} + +void MqttUpdate::writeToCloud() +{ + if (!m_provider) { + qWarning("MqttUpdate: sensor data provider not set. Data not updated"); + return; + } + + if (m_client->state() == QMqttClient::Disconnected) { + qCDebug(boot2QtDemos) << "Disconnected, need to reconnect"; + m_client->connectToHost(); + } + if (m_client->state() == QMqttClient::Connected) { + static bool sendTypeVersion = true; + if (sendTypeVersion) { + m_client->publish(m_topicPrefix + QLatin1String("type"), m_provider->sensorType()); + m_client->publish(m_topicPrefix + QLatin1String("version"), m_provider->versionString()); + sendTypeVersion = false; + } + m_client->publish(m_topicPrefix + QLatin1String("humid"), + QString("%1").arg(m_provider->getRelativeHumidity(), 0, 'f', ROUNDING_DECIMALS)); + m_client->publish(m_topicPrefix + QLatin1String("light"), + QString("%1").arg(m_provider->getLightIntensityLux(), 0, 'f', ROUNDING_DECIMALS)); + + const QString temperature = QString("%1_%2").arg(m_provider->getInfraredAmbientTemperature(), 0, 'f', ROUNDING_DECIMALS) + .arg(m_provider->getInfraredObjectTemperature(), 0, 'f', ROUNDING_DECIMALS); + m_client->publish(m_topicPrefix + QLatin1String("temperature"), temperature); + + const QString baro = QString("%1_%2").arg(m_provider->getBarometerCelsiusTemperature(), 0, 'f', ROUNDING_DECIMALS) + .arg(m_provider->getBarometer_hPa(), 0, 'f', ROUNDING_DECIMALS); + m_client->publish(m_topicPrefix + QLatin1String("barometer"), baro); + + const QString gyro = QString("%1_%2_%3").arg(m_provider->getGyroscopeX_degPerSec(), 0, 'f', ROUNDING_DECIMALS) + .arg(m_provider->getGyroscopeY_degPerSec(), 0, 'f', ROUNDING_DECIMALS) + .arg(m_provider->getGyroscopeZ_degPerSec(), 0, 'f', ROUNDING_DECIMALS); + m_client->publish(m_topicPrefix + QLatin1String("gyro"), gyro); + + const QString accel = QString("%1_%2_%3").arg(m_provider->getAccelometer_xAxis(), 0, 'f', ROUNDING_DECIMALS) + .arg(m_provider->getAccelometer_yAxis(), 0, 'f', ROUNDING_DECIMALS) + .arg(m_provider->getAccelometer_zAxis(), 0, 'f', ROUNDING_DECIMALS); + m_client->publish(m_topicPrefix + QLatin1String("accel"), accel); + + const QString magnet = QString("%1_%2_%3").arg(m_provider->getMagnetometerMicroT_xAxis(), 0, 'f', ROUNDING_DECIMALS) + .arg(m_provider->getMagnetometerMicroT_yAxis(), 0, 'f', ROUNDING_DECIMALS) + .arg(m_provider->getMagnetometerMicroT_zAxis(), 0, 'f', ROUNDING_DECIMALS); + m_client->publish(m_topicPrefix + QLatin1String("magnet"), magnet); + + const QString rotation = QString("%1_%2_%3").arg(m_provider->getRotationX(), 0, 'f', ROUNDING_DECIMALS) + .arg(m_provider->getRotationY(), 0, 'f', ROUNDING_DECIMALS) + .arg(m_provider->getRotationZ(), 0, 'f', ROUNDING_DECIMALS); + m_client->publish(m_topicPrefix + QLatin1String("rotation"), rotation); + } +} +#endif //MQTT_TIMER_BASED_PUBLISH + +MqttEventHandler::MqttEventHandler(const QString &name, QObject *parent) + : QObject(parent) +{ + m_deviceName = name; + m_topicPrefix = QString::fromLocal8Bit("sensors/%1/").arg(m_deviceName); + + m_pingTimer.setInterval(5000); + m_pingTimer.setSingleShot(false); + connect(&m_pingTimer, &QTimer::timeout, this, &MqttEventHandler::sendAlive); + + m_client = new QMqttClient; + m_client->setHostname(QLatin1String(MQTT_BROKER)); + m_client->setPort(MQTT_PORT); + m_client->setUsername(QByteArray(MQTT_USERNAME)); + m_client->setPassword(QByteArray(MQTT_PASSWORD)); + + m_client->setWillMessage(QString::fromLocal8Bit("%1>Offline").arg(m_deviceName).toLocal8Bit()); + m_client->setWillQoS(1); + m_client->setWillRetain(true); + m_client->setWillTopic(QString::fromLocal8Bit("sensors/active")); + connect(m_client, &QMqttClient::connected, this, &MqttEventHandler::clientConnected); +} + +MqttEventHandler::~MqttEventHandler() +{ + m_client->publish(QLatin1String("sensors/active"), + QString::fromLocal8Bit("%1>Offline").arg(m_deviceName).toLocal8Bit(), 1); +} diff --git a/tradeshow/iot-sensortag/mqttupdate.h b/tradeshow/iot-sensortag/mqttupdate.h new file mode 100644 index 0000000..2c2d424 --- /dev/null +++ b/tradeshow/iot-sensortag/mqttupdate.h @@ -0,0 +1,121 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ +#ifndef MQTTUPDATE_H +#define MQTTUPDATE_H + +#include +#include + +class DataProviderPool; +class SensorTagDataProvider; +class QMqttClient; + +//#define MQTT_TIMER_BASED_PUBLISH 1 + +class MqttEventHandler : public QObject +{ + Q_OBJECT +public: + explicit MqttEventHandler(const QString &name, QObject *parent = 0); + ~MqttEventHandler(); + DataProviderPool *m_providerPool; +public slots: + void uploadGyro(); + void uploadTemperature(); + void uploadHumidity(); + void uploadLight(); + void uploadAltitude(); + void uploadBarometer(); + void uploadAccelerometer(); + void uploadMagnetometer(); + void uploadRotation(); + + void clientConnected(); + void sendAlive(); + +private: + QString m_deviceName; + QString m_topicPrefix; + QMqttClient *m_client; + QTimer m_pingTimer; +}; + +class MqttUpdate : public QObject +{ + Q_OBJECT +public: + explicit MqttUpdate(QObject *parent = 0); + + void setDataProviderPool(DataProviderPool *provider); + + int updateInterval() const; + void setUpdateInterval(int interval); + void restart(); + void stop(); + +#ifdef MQTT_TIMER_BASED_PUBLISH +protected: + void timerEvent(QTimerEvent* event); + virtual void writeToCloud(); +#else +#endif + +protected: + DataProviderPool *m_providerPool; +private: + QThread *m_handlerThread; + MqttEventHandler *m_handler; + int m_updateInterval; +#ifdef MQTT_TIMER_BASED_PUBLISH + int m_timerId; +#endif +}; + +#endif // MQTTUPDATE_H -- cgit v1.2.3