1
0
mirror of https://github.com/qt/qtmqtt.git synced 2026-10-25 16:16:26 +00:00

Add initial support for QMqttSubscription

The class handles all subscription related items. Developers can use it
to manage the state of the subscription. Having a subscription object
simplifies message handling, there is no need to do a topic check for
each message received.
This commit is contained in:
Maurice Kalinowski 2017-03-23 08:04:20 +01:00
parent 622b0fa962
commit 8bda237fa0
16 changed files with 701 additions and 12 deletions

View File

@ -1,2 +1,4 @@
TEMPLATE = subdirs
SUBDIRS += simpleclient
SUBDIRS += \
simpleclient \
subscriptions

View File

@ -0,0 +1,11 @@
#include "mainwindow.h"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWindow w;
w.show();
return a.exec();
}

View File

@ -0,0 +1,115 @@
#include "mainwindow.h"
#include "subscriptionwindow.h"
#include "ui_mainwindow.h"
#include <QtCore/QDateTime>
#include <QtMqtt/QMqttClient>
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
m_client = new QMqttClient(this);
m_client->setHostname(ui->lineEditHost->text());
m_client->setPort(ui->spinBoxPort->value());
connect(m_client, &QMqttClient::stateChanged, this, &MainWindow::updateLogStateChange);
connect(m_client, &QMqttClient::disconnected, this, &MainWindow::brokerDisconnected);
connect(m_client, &QMqttClient::subscribed, this, [this]() {
const QString content = QDateTime::currentDateTime().toString()
+ QLatin1String("Subscription Succeeded")
+ QLatin1Char('\n');
ui->editLog->insertPlainText(content);
});
connect(m_client, &QMqttClient::messageReceived, this, [this](const QString &topic, const QString &message) {
const QString content = QDateTime::currentDateTime().toString()
+ QLatin1String(" Received Topic: ")
+ topic
+ QLatin1String(" Message: ")
+ message
+ QLatin1Char('\n');
ui->editLog->insertPlainText(content);
});
connect(m_client, &QMqttClient::pingResponse, this, [this]() {
ui->buttonPing->setEnabled(true);
const QString content = QDateTime::currentDateTime().toString()
+ QLatin1String(" PingResponse")
+ QLatin1Char('\n');
ui->editLog->insertPlainText(content);
});
connect(ui->lineEditHost, &QLineEdit::textChanged, m_client, &QMqttClient::setHostname);
connect(ui->spinBoxPort, SIGNAL(valueChanged(int)), this, SLOT(setClientPort(int)));
updateLogStateChange();
}
MainWindow::~MainWindow()
{
delete ui;
qApp->quit();
}
void MainWindow::on_buttonConnect_clicked()
{
if (m_client->state() == QMqttClient::Disconnected) {
ui->lineEditHost->setEnabled(false);
ui->spinBoxPort->setEnabled(false);
ui->buttonConnect->setText(tr("Disconnect"));
m_client->connectToHost();
} else {
ui->lineEditHost->setEnabled(true);
ui->spinBoxPort->setEnabled(true);
ui->buttonConnect->setText(tr("Connect"));
m_client->disconnectFromHost();
}
}
void MainWindow::on_buttonQuit_clicked()
{
QApplication::quit();
}
void MainWindow::updateLogStateChange()
{
const QString content = QDateTime::currentDateTime().toString()
+ QLatin1String(": State Change")
+ QString::number(m_client->state())
+ QLatin1Char('\n');
ui->editLog->insertPlainText(content);
}
void MainWindow::brokerDisconnected()
{
ui->lineEditHost->setEnabled(true);
ui->spinBoxPort->setEnabled(true);
ui->buttonConnect->setText(tr("Connect"));
}
void MainWindow::setClientPort(int p)
{
m_client->setPort(p);
}
void MainWindow::on_buttonPublish_clicked()
{
m_client->publish(ui->lineEditTopic->text(), ui->lineEditMessage->text());
}
void MainWindow::on_buttonSubscribe_clicked()
{
auto subscription = m_client->subscribe(ui->lineEditTopic->text());
auto subWindow = new SubscriptionWindow(subscription);
subWindow->setWindowTitle(subscription->topic());
subWindow->show();
}
void MainWindow::on_buttonPing_clicked()
{
ui->buttonPing->setEnabled(false);
m_client->requestPing();
}

View File

@ -0,0 +1,39 @@
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QTcpSocket>
#include <QtMqtt/QMqttClient>
namespace Ui {
class MainWindow;
}
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = 0);
~MainWindow();
private slots:
void on_buttonConnect_clicked();
void on_buttonQuit_clicked();
void updateLogStateChange();
void brokerDisconnected();
void setClientPort(int p);
void on_buttonPublish_clicked();
void on_buttonSubscribe_clicked();
void on_buttonPing_clicked();
private:
Ui::MainWindow *ui;
QMqttClient *m_client;
};
#endif // MAINWINDOW_H

View File

@ -0,0 +1,190 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>MainWindow</class>
<widget class="QMainWindow" name="MainWindow">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>629</width>
<height>595</height>
</rect>
</property>
<property name="windowTitle">
<string>MainWindow</string>
</property>
<widget class="QWidget" name="centralWidget">
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<layout class="QFormLayout" name="formLayout">
<item row="0" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>Host:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLineEdit" name="lineEditHost">
<property name="text">
<string>hostprocess.de</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
<string>Port:</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QSpinBox" name="spinBoxPort">
<property name="maximum">
<number>99999</number>
</property>
<property name="value">
<number>1883</number>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QPushButton" name="buttonConnect">
<property name="text">
<string>Connect</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="1">
<widget class="QLineEdit" name="lineEditTopic">
<property name="text">
<string>qtmqtt/topic1</string>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="label_3">
<property name="text">
<string>Topic:</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_4">
<property name="text">
<string>Message:</string>
</property>
</widget>
</item>
<item row="1" column="2">
<widget class="QPushButton" name="buttonPublish">
<property name="text">
<string>Publish</string>
</property>
</widget>
</item>
<item row="0" column="2">
<widget class="QPushButton" name="buttonSubscribe">
<property name="text">
<string>Subscribe</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLineEdit" name="lineEditMessage">
<property name="text">
<string>This is a test message</string>
</property>
</widget>
</item>
<item row="2" column="2">
<widget class="QPushButton" name="buttonPing">
<property name="text">
<string>Ping</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QGroupBox" name="groupBox">
<property name="title">
<string>Log Messages</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QPlainTextEdit" name="editLog"/>
</item>
</layout>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_3">
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="buttonQuit">
<property name="text">
<string>Quit</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<widget class="QMenuBar" name="menuBar">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>629</width>
<height>21</height>
</rect>
</property>
<widget class="QMenu" name="menuFile">
<property name="title">
<string>File</string>
</property>
<addaction name="actionQuit"/>
</widget>
<addaction name="menuFile"/>
</widget>
<widget class="QToolBar" name="mainToolBar">
<attribute name="toolBarArea">
<enum>TopToolBarArea</enum>
</attribute>
<attribute name="toolBarBreak">
<bool>false</bool>
</attribute>
</widget>
<widget class="QStatusBar" name="statusBar"/>
<action name="actionQuit">
<property name="text">
<string>Quit</string>
</property>
</action>
</widget>
<layoutdefault spacing="6" margin="11"/>
<resources/>
<connections/>
</ui>

View File

@ -0,0 +1,27 @@
QT += core gui network widgets mqtt
TARGET = mqttsubscriptions
# The following define makes your compiler emit warnings if you use
# any feature of Qt which as been marked as deprecated (the exact warnings
# depend on your compiler). Please consult the documentation of the
# deprecated API in order to know how to port your code away from it.
DEFINES += QT_DEPRECATED_WARNINGS
# You can also make your code fail to compile if you use deprecated APIs.
# In order to do so, uncomment the following line.
# You can also select to disable deprecated APIs only up to a certain version of Qt.
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0
SOURCES += main.cpp\
mainwindow.cpp \
subscriptionwindow.cpp
HEADERS += mainwindow.h \
subscriptionwindow.h
FORMS += mainwindow.ui \
subscriptionwindow.ui
target.path = $$[QT_INSTALL_EXAMPLES]/mqtt/subscriptions
INSTALLS += target

View File

@ -0,0 +1,47 @@
#include "subscriptionwindow.h"
#include "ui_subscriptionwindow.h"
SubscriptionWindow::SubscriptionWindow(QSharedPointer<QMqttSubscription> &sub, QWidget *parent) :
QWidget(parent),
ui(new Ui::SubscriptionWindow)
{
m_sub = sub;
ui->setupUi(this);
ui->labelSub->setText(m_sub->topic());
updateStatus(m_sub->state());
connect(m_sub.data(), &QMqttSubscription::messageReceived, this, &SubscriptionWindow::updateMessage);
connect(m_sub.data(), &QMqttSubscription::stateChanged, this, &SubscriptionWindow::updateStatus);
connect(ui->pushButton, &QAbstractButton::clicked, m_sub.data(), &QMqttSubscription::unsubscribe);
}
SubscriptionWindow::~SubscriptionWindow()
{
delete ui;
}
void SubscriptionWindow::updateMessage(const QString &text)
{
ui->listWidget->addItem(text);
}
void SubscriptionWindow::updateStatus(QMqttSubscription::SubscriptionState state)
{
switch (state) {
case QMqttSubscription::Unsubscribed:
ui->labelStatus->setText(QLatin1String("Unsubscribed"));
break;
case QMqttSubscription::Pending:
ui->labelStatus->setText(QLatin1String("Pending"));
break;
case QMqttSubscription::Subscribed:
ui->labelStatus->setText(QLatin1String("Subscribed"));
break;
case QMqttSubscription::Error:
ui->labelStatus->setText(QLatin1String("Error"));
break;
default:
ui->labelStatus->setText(QLatin1String("--Unknown--"));
break;
}
}

View File

@ -0,0 +1,27 @@
#ifndef SUBSCRIPTIONWINDOW_H
#define SUBSCRIPTIONWINDOW_H
#include <QWidget>
#include <QtMqtt/QMqttSubscription>
namespace Ui {
class SubscriptionWindow;
}
class SubscriptionWindow : public QWidget
{
Q_OBJECT
public:
explicit SubscriptionWindow(QSharedPointer<QMqttSubscription> &sub, QWidget *parent = 0);
~SubscriptionWindow();
public slots:
void updateMessage(const QString &text);
void updateStatus(QMqttSubscription::SubscriptionState state);
private:
QSharedPointer<QMqttSubscription> m_sub;
Ui::SubscriptionWindow *ui;
};
#endif // SUBSCRIPTIONWINDOW_H

View File

@ -0,0 +1,63 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>SubscriptionWindow</class>
<widget class="QWidget" name="SubscriptionWindow">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>400</width>
<height>300</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<layout class="QFormLayout" name="formLayout">
<item row="0" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>Subscription:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLabel" name="labelSub">
<property name="text">
<string>TextLabel</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
<string>Status:</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLabel" name="labelStatus">
<property name="text">
<string>TextLabel</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QListWidget" name="listWidget"/>
</item>
<item>
<widget class="QPushButton" name="pushButton">
<property name="text">
<string>Unsubscribe</string>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

View File

@ -18,9 +18,11 @@ PRIVATE_HEADERS += \
SOURCES += \
qmqttclient.cpp \
qmqttconnection.cpp \
qmqttcontrolpacket.cpp
qmqttcontrolpacket.cpp \
qmqttsubscription.cpp
HEADERS += $$PUBLIC_HEADERS $$PRIVATE_HEADERS \
qmqttcontrolpacket_p.h
qmqttcontrolpacket_p.h \
qmqttsubscription.h
load(qt_module)

View File

@ -59,7 +59,7 @@ QIODevice *QMqttClient::transport() const
return d->m_connection.transport();
}
bool QMqttClient::subscribe(const QString &topic)
QSharedPointer<QMqttSubscription> QMqttClient::subscribe(const QString &topic)
{
Q_D(QMqttClient);
return d->m_connection.sendControlSubscribe(topic);

View File

@ -31,9 +31,11 @@
#define QTMQTTCLIENT_H
#include <QtMqtt/qmqttglobal.h>
#include <QtMqtt/QMqttSubscription>
#include <QObject>
#include <QtCore/QIODevice>
#include <QtCore/QSharedPointer>
QT_BEGIN_NAMESPACE
@ -68,7 +70,7 @@ public:
void setTransport(QIODevice *device, TransportType transport);
QIODevice *transport() const;
bool subscribe(const QString& topic);
QSharedPointer<QMqttSubscription> subscribe(const QString& topic);
void unsubscribe(const QString& topic);
bool publish(const QString &topic, const QString& message = QString());

View File

@ -191,8 +191,11 @@ bool QMqttConnection::sendControlPublish(const QString &topic, const QString &me
return writePacketToTransport(packet);
}
bool QMqttConnection::sendControlSubscribe(const QString &topic)
QSharedPointer<QMqttSubscription> QMqttConnection::sendControlSubscribe(const QString &topic)
{
if (m_activeSubscriptions.contains(topic))
return m_activeSubscriptions[topic];
// has to have 0010 as bits 3-0, maybe update SUBSCRIBE instead?
// MQTT-3.8.1-1
const quint8 header = QMqttControlPacket::SUBSCRIBE + 0x02;
@ -206,14 +209,22 @@ bool QMqttConnection::sendControlSubscribe(const QString &topic)
// ### TODO: Add actual QOS
packet.append(char(0)); // QOS
QSharedPointer<QMqttSubscription> result(new QMqttSubscription);
result->m_topic = topic;
result->setState(QMqttSubscription::Pending);
if (!writePacketToTransport(packet))
return QSharedPointer<QMqttSubscription>();
// SUBACK must contain identifier MQTT-3.8.4-2
m_pendingSubscriptionAck.insert(identifier);
return writePacketToTransport(packet);
m_pendingSubscriptionAck.insert(identifier, result);
return result;
}
bool QMqttConnection::sendControlUnsubscribe()
{
Q_UNIMPLEMENTED();
qDebug() << Q_FUNC_INFO;
return false;
}
@ -231,6 +242,10 @@ bool QMqttConnection::sendControlDisconnect()
{
m_pingTimer.stop();
for (auto sub : m_activeSubscriptions)
sub->unsubscribe();
m_activeSubscriptions.clear();
const QMqttControlPacket packet(QMqttControlPacket::DISCONNECT);
if (!writePacketToTransport(packet)) {
qWarning("Could not write DISCONNECT frame to transport");
@ -327,18 +342,21 @@ void QMqttConnection::transportReadReady()
}
quint8 result;
m_transport->read((char*)&result, 1);
auto sub = m_pendingSubscriptionAck.take(id);
if (result <= 2) {
// ### TODO: subscriptionSucceeded/Failed
emit m_client->subscribed();
// 0 Success with QoS 0
// 1 Success with QoS 1
// 2 Success with QoS 2
m_pendingSubscriptionAck.remove(id);
sub->setState(QMqttSubscription::Subscribed);
m_activeSubscriptions.insert(sub->topic(), sub);
} else if (result == 0x80) {
// ### TODO: subscriptionFailed
qWarning("Subscription failed");
sub->setState(QMqttSubscription::Error);
} else {
qWarning("Received invalid SUBACK result value");
sub->setState(QMqttSubscription::Error);
}
break;
}
@ -356,6 +374,11 @@ void QMqttConnection::transportReadReady()
const QString message = QString::fromUtf8(reinterpret_cast<const char *>(m_transport->read(messageLength).constData()));
emit m_client->messageReceived(topic, message);
// Find a subscription
const QMap<QString, QSharedPointer<QMqttSubscription>>::const_iterator sub = m_activeSubscriptions.find(topic);
if (sub != m_activeSubscriptions.constEnd())
emit (*sub)->messageReceived(message);
break;
}
case QMqttControlPacket::PINGRESP: {

View File

@ -43,7 +43,9 @@
#include "qmqttclient.h"
#include "qmqttcontrolpacket_p.h"
#include "qmqttsubscription.h"
#include <QtCore/QObject>
#include <QtCore/QSharedPointer>
#include <QtCore/QSet>
#include <QtCore/QTimer>
#include <QtCore/QtEndian>
@ -79,7 +81,7 @@ public:
bool sendControlConnect();
bool sendControlPublish(const QString &topic, const QString &message);
bool sendControlSubscribe(const QString &topic);
QSharedPointer<QMqttSubscription> sendControlSubscribe(const QString &topic);
bool sendControlUnsubscribe();
bool sendControlPingRequest();
bool sendControlDisconnect();
@ -100,7 +102,8 @@ public:
QMqttClient *m_client{nullptr};
private:
bool writePacketToTransport(const QMqttControlPacket &p);
QSet<quint16> m_pendingSubscriptionAck;
QMap<quint16, QSharedPointer<QMqttSubscription>> m_pendingSubscriptionAck;
QMap<QString, QSharedPointer<QMqttSubscription>> m_activeSubscriptions;
InternalConnectionState m_internalState{BrokerDisconnected};
QTimer m_pingTimer;
};

View File

@ -0,0 +1,67 @@
/****************************************************************************
**
** Copyright (C) 2017 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the Qt Mqtt module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:GPL$
** 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.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 or (at your option) any later version
** approved by the KDE Free Qt Foundation. The licenses are as published by
** the Free Software Foundation and appearing in the file LICENSE.GPL3
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include "qmqttsubscription.h"
QMqttSubscription::QMqttSubscription(QObject *parent) : QObject(parent)
{
}
QMqttSubscription::~QMqttSubscription()
{
if (m_state == Subscribed)
unsubscribe();
}
QMqttSubscription::SubscriptionState QMqttSubscription::state() const
{
return m_state;
}
QString QMqttSubscription::topic() const
{
return m_topic;
}
void QMqttSubscription::setState(QMqttSubscription::SubscriptionState state)
{
if (m_state == state)
return;
m_state = state;
emit stateChanged(m_state);
}
void QMqttSubscription::unsubscribe()
{
Q_UNIMPLEMENTED();
setState(Unsubscribed);
// Do send unsubscribe
// Emit state change to notify all instances
}

View File

@ -0,0 +1,71 @@
/****************************************************************************
**
** Copyright (C) 2017 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the Qt Mqtt module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:GPL$
** 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.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 or (at your option) any later version
** approved by the KDE Free Qt Foundation. The licenses are as published by
** the Free Software Foundation and appearing in the file LICENSE.GPL3
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#ifndef QMQTTSUBSCRIPTION_H
#define QMQTTSUBSCRIPTION_H
#include <QtCore/QObject>
#include <QtMqtt/qmqttglobal.h>
QT_BEGIN_NAMESPACE
class Q_MQTT_EXPORT QMqttSubscription : public QObject
{
Q_OBJECT
Q_PROPERTY(SubscriptionState state READ state WRITE setState NOTIFY stateChanged)
Q_PROPERTY(QString topic READ topic)
public:
~QMqttSubscription();
enum SubscriptionState {
Unsubscribed = 0,
Pending,
Subscribed,
Error
};
SubscriptionState state() const;
QString topic() const;
signals:
void stateChanged(SubscriptionState state);
void messageReceived(const QString msg);
public slots:
void setState(SubscriptionState state);
void unsubscribe();
private:
friend class QMqttConnection;
friend class QMqttClient;
explicit QMqttSubscription(QObject *parent = nullptr);
SubscriptionState m_state;
QString m_topic;
};
QT_END_NAMESPACE
#endif // QMQTTSUBSCRIPTION_H