diff --git a/src/backend/datasources/MQTTClient.cpp b/src/backend/datasources/MQTTClient.cpp index f7f073f46..4d48d611d 100644 --- a/src/backend/datasources/MQTTClient.cpp +++ b/src/backend/datasources/MQTTClient.cpp @@ -1,1400 +1,1368 @@ /*************************************************************************** File : MQTTClient.cpp Project : LabPlot Description : Represents a MQTT Client -------------------------------------------------------------------- Copyright : (C) 2018 Kovacs Ferencz (kferike98@gmail.com) ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ #ifdef HAVE_MQTT #include "backend/datasources/MQTTClient.h" #include "backend/datasources/MQTTSubscription.h" #include "backend/datasources/MQTTTopic.h" #include "backend/datasources/filters/AsciiFilter.h" #include "backend/datasources/filters/FITSFilter.h" #include "backend/datasources/filters/BinaryFilter.h" #include "backend/core/Project.h" #include "kdefrontend/spreadsheet/PlotDataDialog.h" #include "commonfrontend/spreadsheet/SpreadsheetView.h" #include "kdefrontend/datasources/MQTTErrorWidget.h" #include #include #include #include #include #include #include #include #include #include #include /*! \class MQTTClient \brief The MQTT Client connects to the broker set in ImportFileWidget. It manages the MQTTSubscriptions, and the MQTTTopics. \ingroup datasources */ MQTTClient::MQTTClient(const QString& name) : Folder(name), m_paused(false), m_prepared(false), m_keepLastValues(false), m_filter(nullptr), m_updateTimer(new QTimer(this)), m_willTimer(new QTimer(this)), m_client(new QMqttClient(this)), - m_mqttTest(false), - m_mqttRetain(false), - m_mqttUseWill(false), - m_mqttUseID(false), + m_MQTTTest(false), + m_MQTTRetain(false), + m_MQTTUseWill(false), + m_MQTTUseID(false), m_loaded(false), m_sampleSize(1), m_keepNValues(0), m_updateInterval(1000), m_disconnectForWill(false), - m_mqttUseAuthentication(false), + m_MQTTUseAuthentication(false), m_subscriptionsLoaded(0), m_subscriptionCountToLoad(0), - m_mqttFirstConnectEstablished(false) { + m_MQTTFirstConnectEstablished(false) { - qDebug()<<"MQTTClient constructor"; + qDebug() << "MQTTClient constructor: " << m_client->hostname(); connect(m_updateTimer, &QTimer::timeout, this, &MQTTClient::read); m_willStatistics.fill(false, 15); - connect(m_client, &QMqttClient::connected, this, &MQTTClient::onMqttConnect); + connect(m_client, &QMqttClient::connected, this, &MQTTClient::onMQTTConnect); connect(m_willTimer, &QTimer::timeout, this, &MQTTClient::updateWillMessage); - connect(m_client, &QMqttClient::errorChanged, this, &MQTTClient::mqttErrorChanged); + connect(m_client, &QMqttClient::errorChanged, this, &MQTTClient::MQTTErrorChanged); } MQTTClient::~MQTTClient() { emit clientAboutToBeDeleted(m_client->hostname()); //stop reading before deleting the objects pauseReading(); - qDebug()<<"destructor"; + qDebug()<<"Delete MQTTClient: " << m_client->hostname(); if (m_filter) delete m_filter; - qDebug()<<"delete timers"; + delete m_updateTimer; delete m_willTimer; - qDebug()<<"disocnnect"; m_client->disconnectFromHost(); - qDebug()<<"delete client"; delete m_client; } /*! * depending on the update type, periodically or on data changes, starts the timer. */ void MQTTClient::ready() { if (m_updateType == TimeInterval) m_updateTimer->start(m_updateInterval); } /*! * \brief Updates the MQTTTopics of the client */ void MQTTClient::updateNow() { - qDebug()<<"Update now"; m_updateTimer->stop(); read(); if (m_updateType == TimeInterval && !m_paused) m_updateTimer->start(m_updateInterval); } /*! * \brief Continue reading from messages after it was paused. */ void MQTTClient::continueReading() { - qDebug()<<"continue reading"; m_paused = false; if (m_updateType == TimeInterval) m_updateTimer->start(m_updateInterval); } /*! * \brief Pause the reading from messages. */ void MQTTClient::pauseReading() { - qDebug()<<"pause reading"; m_paused = true; if (m_updateType == TimeInterval) m_updateTimer->stop(); } /*! * \brief Sets the filter of the MQTTClient. * * \param f a pointer to the new filter */ void MQTTClient::setFilter(AbstractFileFilter* f) { m_filter = f; } /*! * \brief Returns the filter of the MQTTClient. */ AbstractFileFilter* MQTTClient::filter() const { return m_filter; } /*! * \brief Sets the MQTTclient's update interval to \c interval * \param interval */ void MQTTClient::setUpdateInterval(int interval) { - qDebug()<<"Update interval " << interval; m_updateInterval = interval; if(!m_paused) m_updateTimer->start(m_updateInterval); } /*! * \brief Returns the MQTTClient's update interval to \c interval * \param interval */ int MQTTClient::updateInterval() const { return m_updateInterval; } /*! * \brief Sets how many values we should store * \param keepNValues */ void MQTTClient::setKeepNValues(int keepNValues) { - qDebug()<<"Keep N Values" << keepNValues; m_keepNValues = keepNValues; } /*! * \brief Returns how many values we should store */ int MQTTClient::keepNValues() const { return m_keepNValues; } /*! * \brief Provides information about whether the reading is paused or not * * \return true if the reading is paused * \return false otherwise */ bool MQTTClient::isPaused() const { return m_paused; } /*! * \brief Sets the size rate to sampleSize * \param sampleSize */ void MQTTClient::setSampleSize(int sampleSize) { - qDebug()<<"Sample rate: " << sampleSize; m_sampleSize = sampleSize; } /*! * \brief Returns the size rate */ int MQTTClient::sampleSize() const { return m_sampleSize; } /*! * \brief Sets the MQTTClient's reading type to readingType * \param readingType */ void MQTTClient::setReadingType(ReadingType readingType) { - qDebug()<<"Read Type : " << static_cast(readingType); m_readingType = readingType; } /*! * \brief Returns the MQTTClient's reading type */ MQTTClient::ReadingType MQTTClient::readingType() const { return m_readingType; } /*! * \brief Sets the MQTTClient's update type to updatetype and handles this change * \param updatetype */ void MQTTClient::setUpdateType(UpdateType updatetype) { - qDebug()<<"Update Type : " << static_cast(updatetype); if (updatetype == NewData) { m_updateTimer->stop(); } m_updateType = updatetype; } /*! * \brief Returns the MQTTClient's update type */ MQTTClient::UpdateType MQTTClient::updateType() const { return m_updateType; } /*! * \brief Returns the MQTTClient's icon */ QIcon MQTTClient::icon() const { QIcon icon; icon = QIcon::fromTheme("labplot-MQTT"); return icon; } /*! * \brief Sets the host and port for the client. * * \param host the hostname of the broker we want to connect to * \param port the port used by the broker */ -void MQTTClient::setMqttClientHostPort(const QString& host, const quint16& port) { +void MQTTClient::setMQTTClientHostPort(const QString& host, const quint16& port) { m_client->setHostname(host); m_client->setPort(port); } /*! * \brief Returns hostname of the broker the client is connected to. */ QString MQTTClient::clientHostName() const{ return m_client->hostname(); } /*! * \brief Returns the port used by the broker. */ quint16 MQTTClient::clientPort() const { return m_client->port(); } /*! * \brief Sets the flag on the given value. * If set true it means that the broker requires authentication, otherwise it doesn't. * * \param use */ void MQTTClient::setMQTTUseAuthentication(bool use) { - m_mqttUseAuthentication = use; + m_MQTTUseAuthentication = use; } /*! * \brief Returns whether the broker requires authentication or not. */ -bool MQTTClient::mqttUseAuthentication() const { - return m_mqttUseAuthentication; +bool MQTTClient::MQTTUseAuthentication() const { + return m_MQTTUseAuthentication; } /*! * \brief Sets the username and password for the client. * * \param username the username used for authentication * \param password the password used for authentication */ -void MQTTClient::setMqttClientAuthentication(const QString& username, const QString& password) { +void MQTTClient::setMQTTClientAuthentication(const QString& username, const QString& password) { m_client->setUsername(username); m_client->setPassword(password); } /*! * \brief Returns the username used for authentication. */ QString MQTTClient::clientUserName() const{ return m_client->username(); } /*! * \brief Returns the password used for authentication. */ QString MQTTClient::clientPassword() const{ return m_client->password(); } /*! * \brief Sets the flag on the given value. * If set true it means that user wants to set the client ID, otherwise it's not the case. * * \param use */ void MQTTClient::setMQTTUseID(bool use) { - m_mqttUseID = use; + m_MQTTUseID = use; } /*! * \brief Returns whether the user wants to set the client ID or not. */ -bool MQTTClient::mqttUseID() const { - return m_mqttUseID; +bool MQTTClient::MQTTUseID() const { + return m_MQTTUseID; } /*! * \brief Sets the ID of the client * * \param id */ -void MQTTClient::setMqttClientId(const QString &id){ +void MQTTClient::setMQTTClientId(const QString &id){ m_client->setClientId(id); } /*! * \brief Returns the ID of the client */ QString MQTTClient::clientID () const{ return m_client->clientId(); } /*! * \brief Sets the flag on the given value. * If retain is true we interpret retain messages, otherwise we do not * * \param retain */ -void MQTTClient::setMqttRetain(bool retain) { - m_mqttRetain = retain; +void MQTTClient::setMQTTRetain(bool retain) { + m_MQTTRetain = retain; } /*! * \brief Returns the flag, which set to true means that interpret retain messages, otherwise we do not */ -bool MQTTClient::mqttRetain() const { - return m_mqttRetain; +bool MQTTClient::MQTTRetain() const { + return m_MQTTRetain; } /*! * \brief Returns the name of every MQTTTopics which already received a message, and is child of the MQTTClient */ QVector MQTTClient::topicNames() const { return m_topicNames; } /*! * \brief Adds the initial subscriptions that were set in ImportFileWidget * * \param filter the name of the subscribed topic * \param qos the qos level of the subscription */ -void MQTTClient::addInitialMqttSubscriptions(const QMqttTopicFilter& filter, const quint8& qos) { +void MQTTClient::addInitialMQTTSubscriptions(const QMqttTopicFilter& filter, const quint8& qos) { m_subscribedTopicNameQoS[filter] = qos; } /*! * \brief Returns the name of every MQTTSubscription of the MQTTClient */ -QVector MQTTClient::mqttSubscriptions() const { +QVector MQTTClient::MQTTSubscriptions() const { return m_subscriptions; } /*! * \brief Adds a new MQTTSubscription to the MQTTClient * * \param topic, the name of the topic * \param QoS */ void MQTTClient::addMQTTSubscription(const QString& topic, quint8 QoS) { //Check whether the subscription already exists, if not we can add it if(!m_subscriptions.contains(topic)) { QMqttTopicFilter filter {topic}; QMqttSubscription* temp = m_client->subscribe(filter, QoS); if (temp) { qDebug()<<"Subscribe to: "<< temp->topic() << " " << temp->qos(); m_subscriptions.push_back(temp->topic().filter()); m_subscribedTopicNameQoS[temp->topic().filter()] = temp->qos(); MQTTSubscription* newSubscription = new MQTTSubscription(temp->topic().filter()); newSubscription->setMQTTClient(this); addChild(newSubscription); - m_mqttSubscriptions.push_back(newSubscription); + m_MQTTSubscriptions.push_back(newSubscription); //Search for inferior subscriptions, that the new subscription contains bool found = false; QVector inferiorSubscriptions; - for(int i = 0; i < m_mqttSubscriptions.size(); ++i) { - if(checkTopicContains(topic, m_mqttSubscriptions[i]->subscriptionName()) - && topic != m_mqttSubscriptions[i]->subscriptionName()) { + for(int i = 0; i < m_MQTTSubscriptions.size(); ++i) { + if(checkTopicContains(topic, m_MQTTSubscriptions[i]->subscriptionName()) + && topic != m_MQTTSubscriptions[i]->subscriptionName()) { found = true; - inferiorSubscriptions.push_back(m_mqttSubscriptions[i]); + inferiorSubscriptions.push_back(m_MQTTSubscriptions[i]); } } //If there are some inferior subscripitons, we have to deal with them if(found) { for(int sub = 0; sub < inferiorSubscriptions.size(); ++sub) { - qDebug()<<"Inferior subscription: "<subscriptionName(); + qDebug()<<"Reparent topics of inferior subscription: "<subscriptionName(); + //We have to reparent every topic of the inferior subscription, so no data is lost QVector topics = inferiorSubscriptions[sub]->topics(); - qDebug()<< topics.size(); for(int i = 0; i < topics.size() ; ++i) { - qDebug()<topicName(); topics[i]->reparent(newSubscription); } //Then remove the subscription and every connected informaiton QMqttTopicFilter unsubscribeFilter {inferiorSubscriptions[sub]->subscriptionName()}; m_client->unsubscribe(unsubscribeFilter); - for (int j = 0; j < m_mqttSubscriptions.size(); ++j) { - if(m_mqttSubscriptions[j]->subscriptionName() == + for (int j = 0; j < m_MQTTSubscriptions.size(); ++j) { + if(m_MQTTSubscriptions[j]->subscriptionName() == inferiorSubscriptions[sub]->subscriptionName()) { - m_mqttSubscriptions.remove(j); + m_MQTTSubscriptions.remove(j); } } m_subscriptions.removeAll(inferiorSubscriptions[sub]->subscriptionName()); m_subscribedTopicNameQoS.remove(inferiorSubscriptions[sub]->subscriptionName()); removeChild(inferiorSubscriptions[sub]); } } - connect(temp, &QMqttSubscription::messageReceived, this, &MQTTClient::mqttSubscriptionMessageReceived); + connect(temp, &QMqttSubscription::messageReceived, this, &MQTTClient::MQTTSubscriptionMessageReceived); - emit mqttTopicsChanged(); + emit MQTTTopicsChanged(); } } } /*! * \brief Removes a MQTTSubscription from the MQTTClient * * \param name, the name of the subscription to remove */ void MQTTClient::removeMQTTSubscription(const QString &name) { //We can only remove the subscription if it exists if(m_subscriptions.contains(name)) { //unsubscribe from the topic QMqttTopicFilter filter{name}; m_client->unsubscribe(filter); - qDebug()<<"unsubscribe from: " << name; + qDebug()<<"Unsubscribe from: " << name; //Remove every connected information m_subscriptions.removeAll(name); - for (int i = 0; i < m_mqttSubscriptions.size(); ++i) { - if(m_mqttSubscriptions[i]->subscriptionName() == name) { - MQTTSubscription* removeSubscription = m_mqttSubscriptions[i]; - m_mqttSubscriptions.remove(i); + for (int i = 0; i < m_MQTTSubscriptions.size(); ++i) { + if(m_MQTTSubscriptions[i]->subscriptionName() == name) { + MQTTSubscription* removeSubscription = m_MQTTSubscriptions[i]; + m_MQTTSubscriptions.remove(i); //Remove every topic of the subscription as well QVector topics = removeSubscription->topics(); for (int j = 0; j < topics.size(); ++j) { m_topicNames.removeAll(topics[j]->topicName()); } //Remove the MQTTSubscription removeChild(removeSubscription); break; } } QMapIterator j(m_subscribedTopicNameQoS); while(j.hasNext()) { j.next(); if(j.key().filter() == name) { m_subscribedTopicNameQoS.remove(j.key()); break; } } //Signal that there was a change among the topics - emit mqttTopicsChanged(); + emit MQTTTopicsChanged(); } } /*! * \brief Adds a MQTTSubscription to the MQTTClient *Used when the user unsubscribes from a topic of a MQTTSubscription * * \param topic, the name of the topic * \param QoS */ void MQTTClient::addBeforeRemoveSubscription(const QString &topic, quint8 QoS) { //We can't add the subscription if it already exists if(!m_subscriptions.contains(topic)) { //Subscribe to the topic QMqttTopicFilter filter {topic}; QMqttSubscription* temp = m_client->subscribe(filter, QoS); if (temp) { //Add the MQTTSubscription and other connected data - qDebug()<<"Add before remove: " << temp->topic() << " " << temp->qos(); + qDebug()<<"Add subscription before remove: " << temp->topic() << " " << temp->qos(); m_subscriptions.push_back(temp->topic().filter()); m_subscribedTopicNameQoS[temp->topic().filter()] = temp->qos(); MQTTSubscription* newSubscription = new MQTTSubscription(temp->topic().filter()); newSubscription->setMQTTClient(this); addChild(newSubscription); - m_mqttSubscriptions.push_back(newSubscription); + m_MQTTSubscriptions.push_back(newSubscription); //Search for the subscription the topic belonged to bool found = false; MQTTSubscription* superiorSubscription; - for(int i = 0; i < m_mqttSubscriptions.size(); ++i) { - if(checkTopicContains(m_mqttSubscriptions[i]->subscriptionName(), topic) - && topic != m_mqttSubscriptions[i]->subscriptionName()) { + for(int i = 0; i < m_MQTTSubscriptions.size(); ++i) { + if(checkTopicContains(m_MQTTSubscriptions[i]->subscriptionName(), topic) + && topic != m_MQTTSubscriptions[i]->subscriptionName()) { found = true; - superiorSubscription = m_mqttSubscriptions[i]; + superiorSubscription = m_MQTTSubscriptions[i]; break; } } if(found) { //Search for topics belonging to the superior(old) subscription //which are also contained by the new subscription QVector topics = superiorSubscription->topics(); qDebug()<< topics.size(); QVector inferiorTopics; for(int i = 0; i < topics.size(); ++i) { if(checkTopicContains(topic, topics[i]->topicName())) { inferiorTopics.push_back(topics[i]); } } //Reparent these topics, in order to avoid data loss for(int i = 0; i < inferiorTopics.size() ; ++i) { inferiorTopics[i]->reparent(newSubscription); } } - connect(temp, &QMqttSubscription::messageReceived, this, &MQTTClient::mqttSubscriptionMessageReceived); + connect(temp, &QMqttSubscription::messageReceived, this, &MQTTClient::MQTTSubscriptionMessageReceived); } } } /*! * \brief Reparents the given MQTTTopic to the given MQTTSubscription * * \param topic, the name of the MQTTTopic * \param parent, the name of the MQTTSubscription */ void MQTTClient::reparentTopic(const QString& topic, const QString& parent) { //We can only reparent if the parent containd the topic if(m_subscriptions.contains(parent) && m_topicNames.contains(topic)) { qDebug() << "Reparent " << topic << " to " << parent; //search for the parent MQTTSubscription bool found = false; MQTTSubscription* superiorSubscription; - for(int i = 0; i < m_mqttSubscriptions.size(); ++i) { - if(m_mqttSubscriptions[i]->subscriptionName() == parent) { + for(int i = 0; i < m_MQTTSubscriptions.size(); ++i) { + if(m_MQTTSubscriptions[i]->subscriptionName() == parent) { found = true; - superiorSubscription = m_mqttSubscriptions[i]; + superiorSubscription = m_MQTTSubscriptions[i]; break; } } if(found) { //get every topic of the MQTTClient QVector topics = children(AbstractAspect::Recursive); //Search for the given topic among the MQTTTopics for(int i = 0; i < topics.size(); ++i) { if(topic == topics[i]->topicName()) { //if found, it is reparented to the parent MQTTSubscription topics[i]->reparent(superiorSubscription); break; } } } } } /*! *\brief Checks if a topic contains another one * * \param superior the name of a topic * \param inferior the name of a topic * \return true if superior is equal to or contains(if superior contains wildcards) inferior, * false otherwise */ bool MQTTClient::checkTopicContains(const QString &superior, const QString& inferior) { if (superior == inferior) return true; else { if(superior.contains("/")) { QStringList superiorList = superior.split('/', QString::SkipEmptyParts); QStringList inferiorList = inferior.split('/', QString::SkipEmptyParts); //a longer topic can't contain a shorter one if(superiorList.size() > inferiorList.size()) return false; bool ok = true; for(int i = 0; i < superiorList.size(); ++i) { if(superiorList.at(i) != inferiorList.at(i)) { if((superiorList.at(i) != "+") && !(superiorList.at(i) == "#" && i == superiorList.size() - 1)) { - qDebug() < 0) { for(int j = differIndex +1; j < firstList.size(); ++j) { if(firstList.at(j) != secondtList.at(j)) { differ = true; break; } } } else differ = true; if(!differ) { for(int i = 0; i < firstList.size(); ++i) { if(i != differIndex) { commonTopic.append(firstList.at(i)); } else { //we put "+" wildcard at the level where they differ commonTopic.append("+"); } if(i != firstList.size() - 1) commonTopic.append("/"); } } } } - qDebug() << "Common topic: "<stop(); } /*! * \brief Returns whether the user wants to use will message or not */ -bool MQTTClient::mqttWillUse() const{ - return m_mqttUseWill; +bool MQTTClient::MQTTWillUse() const{ + return m_MQTTUseWill; } /*! * \brief Sets the will topic of the client * * \param topic */ void MQTTClient::setWillTopic(const QString& topic) { m_willTopic = topic; } /*! * \brief Returns the will topic of the client */ QString MQTTClient::willTopic() const{ return m_willTopic; } /*! * \brief Sets the retain flag of the client's will message * * \param retain */ void MQTTClient::setWillRetain(bool retain) { m_willRetain = retain; } /*! * \brief Returns the retain flag of the client's will message */ bool MQTTClient::willRetain() const { return m_willRetain; } /*! * \brief Sets the QoS level of the client's will message * * \param QoS */ void MQTTClient::setWillQoS(quint8 QoS) { m_willQoS = QoS; } /*! * \brief Returns the QoS level of the client's will message */ quint8 MQTTClient::willQoS() const { return m_willQoS; } /*! * \brief Sets the will message type of the client * * \param messageType */ void MQTTClient::setWillMessageType(WillMessageType messageType) { m_willMessageType = messageType; } /*! * \brief Returns the will message type of the client */ MQTTClient::WillMessageType MQTTClient::willMessageType() const { return m_willMessageType; } /*! * \brief Sets the own will message of the user * * \param ownMessage */ void MQTTClient::setWillOwnMessage(const QString& ownMessage) { m_willOwnMessage = ownMessage; } /*! * \brief Returns the own will message of the user */ QString MQTTClient::willOwnMessage() const { return m_willOwnMessage; } /*! * \brief Updates the will message of the client */ void MQTTClient::updateWillMessage() { QVector topics = children(AbstractAspect::Recursive); const AsciiFilter* asciiFilter = nullptr; const MQTTTopic* willTopic = nullptr; - qDebug()<<"Searching for topic"; + //Search for the will topic for (int i = 0; i < topics.count(); ++i) { if(topics[i]->topicName() == m_willTopic) { willTopic = topics[i]; break; } } //if the will topic is found we can update the will message if(willTopic != nullptr) { //To update the will message we have to disconnect first, then after setting everything connect again - if(m_mqttUseWill && (m_client->state() == QMqttClient::ClientState::Connected) ) { + if(m_MQTTUseWill && (m_client->state() == QMqttClient::ClientState::Connected) ) { //Disconnect only once (disconnecting may take a while) if(!m_disconnectForWill) { - qDebug() << "Disconnecting from host"; + qDebug() << "Disconnecting from host in order to update will message"; m_client->disconnectFromHost(); m_disconnectForWill = true; } //Try to update again updateWillMessage(); } //If client is disconnected we can update the settings - else if(m_mqttUseWill && (m_client->state() == QMqttClient::ClientState::Disconnected) && m_disconnectForWill) { + else if(m_MQTTUseWill && (m_client->state() == QMqttClient::ClientState::Disconnected) && m_disconnectForWill) { m_client->setWillQoS(m_willQoS); qDebug()<<"Will QoS" << m_willQoS; m_client->setWillRetain(m_willRetain); qDebug()<<"Will retain" << m_willRetain; m_client->setWillTopic(m_willTopic); qDebug()<<"Will Topic" << m_willTopic; //Set the will message according to m_willMessageType switch (m_willMessageType) { case WillMessageType::OwnMessage: m_client->setWillMessage(m_willOwnMessage.toUtf8()); qDebug()<<"Will own message" << m_willOwnMessage; break; case WillMessageType::Statistics: { - qDebug()<<"Start will statistics"; asciiFilter = dynamic_cast(willTopic->filter()); //If the topic's asciiFilter was found, get the needed statistics if(asciiFilter != nullptr) { - qDebug()<<"Checking column mode"; //Statistics is only possible if the data stored in the MQTTTopic is of type integer or numeric if((asciiFilter->mqttColumnMode() == AbstractColumn::ColumnMode::Integer) || (asciiFilter->mqttColumnMode() == AbstractColumn::ColumnMode::Numeric)) { m_client->setWillMessage(asciiFilter->mqttColumnStatistics(willTopic).toUtf8()); - qDebug() << "Will statistics message: "<< QString(m_client->willMessage()); } //Otherwise set empty message else { m_client->setWillMessage(QString("").toUtf8()); - qDebug() << "Will statistics message: "<< QString(m_client->willMessage()); } + qDebug() << "Will statistics message: "<< QString(m_client->willMessage()); } break; } case WillMessageType::LastMessage: m_client->setWillMessage(m_willLastMessage.toUtf8()); qDebug()<<"Will last message:\n" << m_willLastMessage; break; default: break; } m_disconnectForWill = false; //Reconnect with the updated message m_client->connectToHost(); - qDebug()<< "Reconnect to host"; + qDebug()<< "Reconnect to host after updating will message"; } } } /*! * \brief Returns the MQTTClient's will update type */ MQTTClient::WillUpdateType MQTTClient::willUpdateType() const{ return m_willUpdateType; } /*! * \brief Sets the MQTTClient's will update type * * \param willUpdateType */ void MQTTClient::setWillUpdateType(WillUpdateType willUpdateType) { m_willUpdateType = willUpdateType; } /*! * \brief Returns the time interval of updating the MQTTClient's will message */ int MQTTClient::willTimeInterval() const{ return m_willTimeInterval; } /*! * \brief Sets the time interval of updating the MQTTClient's will message, if update type is TimePeriod * * \param interval */ void MQTTClient::setWillTimeInterval(int interval) { m_willTimeInterval = interval; } /*! * \brief Clear the lastly received message by the will topic * Called when the will topic is changed */ void MQTTClient::clearLastMessage() { m_willLastMessage.clear(); } /*! * \brief Sets true the corresponding flag of the statistic type, * what means that the given statistic type will be added to the will message * * \param statistics */ void MQTTClient::addWillStatistics(WillStatistics statistic){ m_willStatistics[static_cast(statistic)] = true; } /*! * \brief Sets false the corresponding flag of the statistic type, * what means that the given statistic will no longer be added to the will message * * \param statistics */ void MQTTClient::removeWillStatistics(WillStatistics statistic) { m_willStatistics[static_cast(statistic)] = false; } /*! * \brief Returns a bool vector, meaning which statistic types are included in the will message * If the corresponding value is true, the statistic type is included, otherwise it isn't */ QVector MQTTClient::willStatistics() const{ return m_willStatistics; } /*! * \brief Starts the will timer, which will update the will message */ void MQTTClient::startWillTimer() const{ if(m_willUpdateType == WillUpdateType::TimePeriod) m_willTimer->start(m_willTimeInterval); } /*! * \brief Stops the will timer */ void MQTTClient::stopWillTimer() const{ m_willTimer->stop(); } //############################################################################## //################################# SLOTS #################################### //############################################################################## /*! *\brief called periodically when update type is TimeInterval */ void MQTTClient::read() { if (m_filter == nullptr) return; if (!m_prepared) { - qDebug()<<"Read & Connect"; + qDebug()<<"Connect"; //connect to the broker m_client->connectToHost(); - qDebug()<<"connectTOHost called"; m_prepared = true; } - if((m_client->state() == QMqttClient::ClientState::Connected) && m_mqttFirstConnectEstablished) { + if((m_client->state() == QMqttClient::ClientState::Connected) && m_MQTTFirstConnectEstablished) { qDebug()<<"Read"; //Signal for every MQTTTopic that they can read emit readFromTopics(); } } /*! *\brief called when the client successfully connected to the broker */ -void MQTTClient::onMqttConnect() { - qDebug() << "on mqtt connect"; +void MQTTClient::onMQTTConnect() { if(m_client->error() == QMqttClient::NoError) { //if this is the first connection (after setting the options in ImportFileWidget or loading saved project) - if(!m_mqttFirstConnectEstablished) { + if(!m_MQTTFirstConnectEstablished) { qDebug()<<"connection made in MQTTClient"; //Subscribe to initial or loaded topics QMapIterator i(m_subscribedTopicNameQoS); while(i.hasNext()) { i.next(); qDebug()<subscribe(i.key(), i.value()); if(temp) { - qDebug()<topic()<<" "<qos(); //If we didn't load the MQTTClient from xml we have to add the MQTTSubscriptions if(!m_loaded) { m_subscriptions.push_back(temp->topic().filter()); - qDebug()<<"New MQTTSubscription"; MQTTSubscription* newSubscription = new MQTTSubscription(temp->topic().filter()); newSubscription->setMQTTClient(this); - qDebug()<<"Add child"; addChild(newSubscription); - qDebug()<<"Add to vector"; - m_mqttSubscriptions.push_back(newSubscription); + m_MQTTSubscriptions.push_back(newSubscription); } - connect(temp, &QMqttSubscription::messageReceived, this, &MQTTClient::mqttSubscriptionMessageReceived); - qDebug()<<"Added topic"; + connect(temp, &QMqttSubscription::messageReceived, this, &MQTTClient::MQTTSubscriptionMessageReceived); } } - m_mqttFirstConnectEstablished = true; + m_MQTTFirstConnectEstablished = true; //Signal that the initial subscriptions were made - emit mqttSubscribed(); + emit MQTTSubscribed(); } //if there was already a connection made(happens after updating will message) else { - qDebug() << "Resubscribing after will set"; + qDebug() << "Start resubscribing after will message update"; //Only the client has to make the subscriptions again, every other connected data is still avialable QMapIterator i(m_subscribedTopicNameQoS); while(i.hasNext()) { i.next(); QMqttSubscription *temp = m_client->subscribe(i.key(), i.value()); if(temp) { qDebug()<topic()<<" "<qos(); - connect(temp, &QMqttSubscription::messageReceived, this, &MQTTClient::mqttSubscriptionMessageReceived); + connect(temp, &QMqttSubscription::messageReceived, this, &MQTTClient::MQTTSubscriptionMessageReceived); } else - qDebug()<<"Couldn't subscribe after will change"; + qDebug()<<"Couldn't subscribe after will update"; } } } } /*! *\brief called when a message is received by a topic belonging to one of subscriptions of the client. * It passes the message to the appropriate MQTTSubscription which will pass it to the appropriate MQTTTopic */ -void MQTTClient::mqttSubscriptionMessageReceived(const QMqttMessage& msg) { +void MQTTClient::MQTTSubscriptionMessageReceived(const QMqttMessage& msg) { //Decide to interpret retain message or not - if(!msg.retain() || (msg.retain() && m_mqttRetain) ) { - qDebug()<<"message received from "<subscriptionName(), msg.topic().name())) { - m_mqttSubscriptions[i]->messageArrived(QString(msg.payload()), msg.topic().name()); + for(int i = 0; i < m_MQTTSubscriptions.count(); ++i){ + if(checkTopicContains(m_MQTTSubscriptions[i]->subscriptionName(), msg.topic().name())) { + m_MQTTSubscriptions[i]->messageArrived(QString(msg.payload()), msg.topic().name()); break; } } //if the message was received by the will topic, update the last message received by it if(msg.topic().name() == m_willTopic) m_willLastMessage = QString(msg.payload()); } } /*! *\brief Handles some of the possible errors of the client, using MQTTErrorWidget */ -void MQTTClient::mqttErrorChanged(QMqttClient::ClientError clientError) { +void MQTTClient::MQTTErrorChanged(QMqttClient::ClientError clientError) { if(clientError != QMqttClient::ClientError::NoError) { MQTTErrorWidget* errorWidget = new MQTTErrorWidget(clientError, this); errorWidget->show(); } } /*! *\brief Called when a subscription is loaded. * Checks whether every saved subscription was loaded or not. * If everything is loaded, it makes the conneciton and starts the reading * * \param name, the name of the subscription */ void MQTTClient::subscriptionLoaded(const QString &name) { - qDebug()<writeStartElement("MQTTClient"); writeBasicAttributes(writer); writeCommentElement(writer); //general writer->writeStartElement("general"); - writer->writeAttribute("subscriptionCount", QString::number(m_mqttSubscriptions.size())); + writer->writeAttribute("subscriptionCount", QString::number(m_MQTTSubscriptions.size())); writer->writeAttribute("updateType", QString::number(m_updateType)); writer->writeAttribute("readingType", QString::number(m_readingType)); writer->writeAttribute("keepValues", QString::number(m_keepNValues)); if (m_updateType == TimeInterval) writer->writeAttribute("updateInterval", QString::number(m_updateInterval)); if (m_readingType != TillEnd) writer->writeAttribute("sampleSize", QString::number(m_sampleSize)); writer->writeAttribute("host", m_client->hostname()); writer->writeAttribute("port", QString::number(m_client->port())); writer->writeAttribute("username", m_client->username()); writer->writeAttribute("pasword", m_client->password()); writer->writeAttribute("clientId", m_client->clientId()); - writer->writeAttribute("useRetain", QString::number(m_mqttRetain)); - writer->writeAttribute("useWill", QString::number(m_mqttUseWill)); + writer->writeAttribute("useRetain", QString::number(m_MQTTRetain)); + writer->writeAttribute("useWill", QString::number(m_MQTTUseWill)); writer->writeAttribute("willTopic", m_willTopic); writer->writeAttribute("willOwnMessage", m_willOwnMessage); writer->writeAttribute("willQoS", QString::number(m_willQoS)); writer->writeAttribute("willRetain", QString::number(m_willRetain)); writer->writeAttribute("willMessageType", QString::number(static_cast(m_willMessageType))); writer->writeAttribute("willUpdateType", QString::number(static_cast(m_willUpdateType))); writer->writeAttribute("willTimeInterval", QString::number(m_willTimeInterval)); for( int i = 0; i < m_willStatistics.count(); ++i){ writer->writeAttribute("willStatistics"+QString::number(i), QString::number(m_willStatistics[i])); } - writer->writeAttribute("useID", QString::number(m_mqttUseID)); - writer->writeAttribute("useAuthentication", QString::number(m_mqttUseAuthentication)); + writer->writeAttribute("useID", QString::number(m_MQTTUseID)); + writer->writeAttribute("useAuthentication", QString::number(m_MQTTUseAuthentication)); writer->writeEndElement(); //filter m_filter->save(writer); //MQTTSubscription for(auto* sub : children(IncludeHidden)) sub->save(writer); writer->writeEndElement(); // "MQTTClient" } /*! Loads from XML. */ bool MQTTClient::load(XmlStreamReader* reader, bool preview) { - qDebug()<<"Start loading MQTTClient"; if (!readBasicAttributes(reader)) return false; QString attributeWarning = i18n("Attribute '%1' missing or empty, default value is used"); QXmlStreamAttributes attribs; QString str; while (!reader->atEnd()) { reader->readNext(); if (reader->isEndElement() && reader->name() == "MQTTClient") break; if (!reader->isStartElement()) continue; if (reader->name() == "comment") { if (!readCommentElement(reader)) return false; } else if (reader->name() == "general") { - qDebug()<<"MQTTClient general"; attribs = reader->attributes(); str = attribs.value("subscriptionCount").toString(); if(str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'subscriptionCount'")); else m_subscriptionCountToLoad = str.toInt(); str = attribs.value("keepValues").toString(); if(str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'keepValues'")); else m_keepNValues = str.toInt(); str = attribs.value("updateType").toString(); if(str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'updateType'")); else m_updateType = static_cast(str.toInt()); str = attribs.value("readingType").toString(); if(str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'readingType'")); else m_readingType = static_cast(str.toInt()); if (m_updateType == TimeInterval) { str = attribs.value("updateInterval").toString(); if(str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'updateInterval'")); else m_updateInterval = str.toInt(); } if (m_readingType != TillEnd) { str = attribs.value("sampleSize").toString(); if(str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'sampleSize'")); else m_sampleSize = str.toInt(); } str = attribs.value("host").toString(); if(str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'host'")); else m_client->setHostname(str); str =attribs.value("port").toString(); if(str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'port'")); else m_client->setPort(str.toUInt()); str = attribs.value("useAuthentication").toString(); if(str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'useAuthentication'")); else - m_mqttUseAuthentication = str.toInt(); + m_MQTTUseAuthentication = str.toInt(); - if(m_mqttUseAuthentication) { + if(m_MQTTUseAuthentication) { str =attribs.value("username").toString(); if(!str.isEmpty()) m_client->setUsername(str); str =attribs.value("password").toString(); if(!str.isEmpty()) m_client->setPassword(str); } str = attribs.value("useID").toString(); if(str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'useID'")); else - m_mqttUseID = str.toInt(); + m_MQTTUseID = str.toInt(); - if(m_mqttUseID) { + if(m_MQTTUseID) { str =attribs.value("clientId").toString(); if(!str.isEmpty()) m_client->setClientId(str); } str =attribs.value("useRetain").toString(); if(str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'useRetain'")); else - m_mqttRetain = str.toInt(); + m_MQTTRetain = str.toInt(); str =attribs.value("useWill").toString(); if(str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'useWill'")); else - m_mqttUseWill = str.toInt(); + m_MQTTUseWill = str.toInt(); - if(m_mqttUseWill) { + if(m_MQTTUseWill) { str =attribs.value("willTopic").toString(); if(str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'willTopic'")); else m_willTopic = str; str =attribs.value("willOwnMessage").toString(); if(str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'willOwnMessage'")); else m_willOwnMessage = str; str =attribs.value("willQoS").toString(); if(str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'willQoS'")); else m_willQoS = str.toUInt(); str =attribs.value("willRetain").toString(); if(str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'willRetain'")); else m_willRetain = str.toInt(); str =attribs.value("willMessageType").toString(); if(str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'willMessageType'")); else m_willMessageType = static_cast(str.toInt()); str =attribs.value("willUpdateType").toString(); if(str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'willUpdateType'")); else m_willUpdateType = static_cast(str.toInt()); str =attribs.value("willTimeInterval").toString(); if(str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'willTimeInterval'")); else m_willTimeInterval = str.toInt(); for( int i = 0; i < m_willStatistics.count(); ++i){ str =attribs.value("willStatistics"+QString::number(i)).toString(); if(str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'willTimeInterval'")); else m_willStatistics[i] = str.toInt(); } } } else if (reader->name() == "asciiFilter") { - qDebug()<<"load filter"; m_filter = new AsciiFilter(); if (!m_filter->load(reader)) return false; } else if(reader->name() == "MQTTSubscription") { - qDebug()<<"Load MQTTSubscription"; MQTTSubscription* subscription = new MQTTSubscription(""); subscription->setMQTTClient(this); connect(subscription, &MQTTSubscription::loaded, this, &MQTTClient::subscriptionLoaded); if (!subscription->load(reader, preview)) { delete subscription; return false; } - m_mqttSubscriptions.push_back(subscription); + m_MQTTSubscriptions.push_back(subscription); addChildFast(subscription); } else {// unknown element reader->raiseWarning(i18n("unknown element '%1'", reader->name().toString())); if (!reader->skipToEndElement()) return false; } } return !reader->hasError(); } #endif diff --git a/src/backend/datasources/MQTTClient.h b/src/backend/datasources/MQTTClient.h index adddab936..b9aa388a6 100644 --- a/src/backend/datasources/MQTTClient.h +++ b/src/backend/datasources/MQTTClient.h @@ -1,267 +1,254 @@ /*************************************************************************** File : MQTTClient.h Project : LabPlot Description : Represents a MQTT Client -------------------------------------------------------------------- Copyright : (C) 2018 Kovacs Ferencz (kferike98@gmail.com) ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ #ifndef MQTTCLIENT_H #define MQTTCLIENT_H #ifdef HAVE_MQTT #include "backend/core/Folder.h" #include #include #include #include #include #include #include #include class QString; class AbstractFileFilter; class MQTTSubscription; class QAction; class MQTTClient : public Folder{ Q_OBJECT public: enum UpdateType { TimeInterval = 0, NewData }; enum ReadingType { ContinuousFixed = 0, FromEnd, TillEnd }; enum WillMessageType { OwnMessage = 0, Statistics, LastMessage }; enum WillUpdateType { TimePeriod = 0, OnClick }; enum WillStatistics { Minimum = 0, Maximum, ArithmeticMean, GeometricMean, HarmonicMean, ContraharmonicMean, Median, Variance, StandardDeviation, MeanDeviation, MeanDeviationAroundMedian, MedianDeviation, Skewness, Kurtosis, Entropy }; explicit MQTTClient(const QString& name); virtual ~MQTTClient() override; void ready(); UpdateType updateType() const; void setUpdateType(UpdateType); ReadingType readingType() const; void setReadingType(ReadingType); int sampleSize() const; void setSampleSize(int); bool isPaused() const; void setUpdateInterval(int); int updateInterval() const; void setKeepNValues(int); int keepNValues() const; void setKeepLastValues(bool); bool keepLastValues() const; - void setMqttClientHostPort(const QString&, const quint16&); - void setMqttClientAuthentication(const QString&, const QString&); - void setMqttClientId(const QString&); - QMqttClient mqttClient() const; + void setMQTTClientHostPort(const QString&, const quint16&); + void setMQTTClientAuthentication(const QString&, const QString&); + void setMQTTClientId(const QString&); - void addInitialMqttSubscriptions(const QMqttTopicFilter&, const quint8&); - QVector mqttSubscriptions() const; + void addInitialMQTTSubscriptions(const QMqttTopicFilter&, const quint8&); + QVector MQTTSubscriptions() const; bool checkTopicContains(const QString& superior, const QString& inferior); QString checkCommonLevel(const QString& first, const QString& second); QString clientHostName() const; quint16 clientPort() const; QString clientPassword() const; QString clientUserName() const; QString clientID () const; void updateNow(); void pauseReading(); void continueReading(); void setFilter(AbstractFileFilter*); AbstractFileFilter* filter() const; QIcon icon() const override; void save(QXmlStreamWriter*) const override; bool load(XmlStreamReader*, bool preview) override; QVector topicNames() const; bool checkAllArrived(); - void setMqttWillUse(bool); - bool mqttWillUse() const; + void setMQTTWillUse(bool); + bool MQTTWillUse() const; void setWillTopic(const QString&); QString willTopic() const; void setWillRetain(bool); bool willRetain() const; void setWillQoS(quint8); quint8 willQoS() const; void setWillMessageType(WillMessageType); WillMessageType willMessageType() const; void setWillOwnMessage(const QString&); QString willOwnMessage() const; WillUpdateType willUpdateType() const; void setWillUpdateType(WillUpdateType); int willTimeInterval() const; void setWillTimeInterval(int); void startWillTimer() const; void stopWillTimer() const; void updateWillMessage() ; - void setMqttRetain(bool); - bool mqttRetain() const; + void setMQTTRetain(bool); + bool MQTTRetain() const; void setMQTTUseID(bool); - bool mqttUseID() const; + bool MQTTUseID() const; void setMQTTUseAuthentication(bool); - bool mqttUseAuthentication() const; + bool MQTTUseAuthentication() const; void clearLastMessage(); void addWillStatistics(WillStatistics); void removeWillStatistics(WillStatistics); QVector willStatistics() const; void addMQTTSubscription(const QString&, quint8); void removeMQTTSubscription(const QString&); void addBeforeRemoveSubscription(const QString&, quint8); void reparentTopic(const QString& topic, const QString& parent); private: - //void initActions(); UpdateType m_updateType; ReadingType m_readingType; - bool m_paused; bool m_prepared; bool m_keepLastValues; - int m_sampleSize; int m_keepNValues; int m_updateInterval; - AbstractFileFilter* m_filter; - QTimer* m_updateTimer; - /* - QAction* m_reloadAction; - QAction* m_toggleLinkAction; - QAction* m_showEditorAction; - QAction* m_showSpreadsheetAction; - QAction* m_plotDataAction;*/ - QMqttClient* m_client; QMap m_subscribedTopicNameQoS; QVector m_subscriptions; QVector m_topicNames; - bool m_mqttTest; - bool m_mqttUseWill; + bool m_MQTTTest; + bool m_MQTTUseWill; QString m_willMessage; QString m_willTopic; bool m_willRetain; quint8 m_willQoS; WillMessageType m_willMessageType; QString m_willOwnMessage; QString m_willLastMessage; QTimer* m_willTimer; int m_willTimeInterval; WillUpdateType m_willUpdateType; QVector m_willStatistics; - bool m_mqttFirstConnectEstablished; - bool m_mqttRetain; - bool m_mqttUseID; - bool m_mqttUseAuthentication; - QVector m_mqttSubscriptions; + bool m_MQTTFirstConnectEstablished; + bool m_MQTTRetain; + bool m_MQTTUseID; + bool m_MQTTUseAuthentication; + QVector m_MQTTSubscriptions; bool m_disconnectForWill; bool m_loaded; int m_subscriptionsLoaded; int m_subscriptionCountToLoad; public slots: void read(); private slots: - void onMqttConnect(); - void mqttSubscriptionMessageReceived(const QMqttMessage&); - void mqttErrorChanged(QMqttClient::ClientError); + void onMQTTConnect(); + void MQTTSubscriptionMessageReceived(const QMqttMessage&); + void MQTTErrorChanged(QMqttClient::ClientError); void subscriptionLoaded(const QString&); signals: - void mqttSubscribed(); - void mqttTopicsChanged(); + void MQTTSubscribed(); + void MQTTTopicsChanged(); void readFromTopics(); void clientAboutToBeDeleted(const QString&); }; #endif #endif // MQTTCLIENT_H diff --git a/src/kdefrontend/datasources/ImportFileWidget.cpp b/src/kdefrontend/datasources/ImportFileWidget.cpp index 1b248c8a5..9654e9989 100644 --- a/src/kdefrontend/datasources/ImportFileWidget.cpp +++ b/src/kdefrontend/datasources/ImportFileWidget.cpp @@ -1,2960 +1,2962 @@ /*************************************************************************** File : ImportFileWidget.cpp Project : LabPlot Description : import file data widget -------------------------------------------------------------------- Copyright : (C) 2009-2018 Stefan Gerlach (stefan.gerlach@uni.kn) Copyright : (C) 2009-2017 Alexander Semke (alexander.semke@web.de) Copyright : (C) 2017-2018 Fabian Kristof (fkristofszabolcs@gmail.com) Copyright : (C) 2018 Kovacs Ferencz (kferike98@gmail.com) ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ #include "ImportFileWidget.h" #include "FileInfoDialog.h" #include "backend/datasources/filters/AsciiFilter.h" #include "backend/datasources/filters/BinaryFilter.h" #include "backend/datasources/filters/HDF5Filter.h" #include "backend/datasources/filters/NetCDFFilter.h" #include "backend/datasources/filters/ImageFilter.h" #include "backend/datasources/filters/FITSFilter.h" #include "backend/datasources/filters/JsonFilter.h" #include "backend/datasources/filters/QJsonModel.h" #include "backend/datasources/filters/NgspiceRawAsciiFilter.h" #include "backend/datasources/filters/NgspiceRawBinaryFilter.h" #include "backend/datasources/filters/ROOTFilter.h" #include "AsciiOptionsWidget.h" #include "BinaryOptionsWidget.h" #include "HDF5OptionsWidget.h" #include "ImageOptionsWidget.h" #include "NetCDFOptionsWidget.h" #include "FITSOptionsWidget.h" #include "JsonOptionsWidget.h" #include "ROOTOptionsWidget.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_MQTT #include "backend/core/Project.h" #include #include #include #include #include #endif /*! \class ImportFileWidget \brief Widget for importing data from a file. \ingroup kdefrontend */ ImportFileWidget::ImportFileWidget(QWidget* parent, const QString& fileName) : QWidget(parent), m_fileName(fileName), m_fileEmpty(false), m_liveDataSource(true), #ifdef HAVE_MQTT m_mqttReadyForPreview (false), m_searching(false), m_searchTimer(new QTimer(this)), m_connectTimeoutTimer(new QTimer(this)), m_client(new QMqttClient(this)), #endif m_suppressRefresh(false) { ui.setupUi(this); #ifdef HAVE_MQTT m_searchTimer->setInterval(10000); m_connectTimeoutTimer->setInterval(5000); #endif QCompleter* completer = new QCompleter(this); completer->setModel(new QDirModel); ui.leFileName->setCompleter(completer); ui.cbFileType->addItems(AbstractFileFilter::fileTypes()); QStringList filterItems; filterItems << i18n("Automatic") << i18n("Custom"); ui.cbFilter->addItems(filterItems); // file type specific option widgets QWidget* asciiw = new QWidget(); m_asciiOptionsWidget = std::unique_ptr(new AsciiOptionsWidget(asciiw)); ui.swOptions->insertWidget(AbstractFileFilter::Ascii, asciiw); QWidget* binaryw = new QWidget(); m_binaryOptionsWidget = std::unique_ptr(new BinaryOptionsWidget(binaryw)); ui.swOptions->insertWidget(AbstractFileFilter::Binary, binaryw); QWidget* imagew = new QWidget(); m_imageOptionsWidget = std::unique_ptr(new ImageOptionsWidget(imagew)); ui.swOptions->insertWidget(AbstractFileFilter::Image, imagew); QWidget* hdf5w = new QWidget(); m_hdf5OptionsWidget = std::unique_ptr(new HDF5OptionsWidget(hdf5w, this)); ui.swOptions->insertWidget(AbstractFileFilter::HDF5, hdf5w); QWidget* netcdfw = new QWidget(); m_netcdfOptionsWidget = std::unique_ptr(new NetCDFOptionsWidget(netcdfw, this)); ui.swOptions->insertWidget(AbstractFileFilter::NETCDF, netcdfw); QWidget* fitsw = new QWidget(); m_fitsOptionsWidget = std::unique_ptr(new FITSOptionsWidget(fitsw, this)); ui.swOptions->insertWidget(AbstractFileFilter::FITS, fitsw); QWidget* jsonw = new QWidget(); m_jsonOptionsWidget = std::unique_ptr(new JsonOptionsWidget(jsonw, this)); ui.swOptions->insertWidget(AbstractFileFilter::Json, jsonw); QWidget* rootw = new QWidget(); m_rootOptionsWidget = std::unique_ptr(new ROOTOptionsWidget(rootw, this)); ui.swOptions->insertWidget(AbstractFileFilter::ROOT, rootw); ui.tvJson->header()->setSectionResizeMode(QHeaderView::ResizeToContents); ui.tvJson->setAlternatingRowColors(true); ui.tvJson->setModel(m_jsonOptionsWidget->model()); showJsonModel(false); // the table widget for preview m_twPreview = new QTableWidget(ui.tePreview); m_twPreview->verticalHeader()->hide(); m_twPreview->setEditTriggers(QTableWidget::NoEditTriggers); QHBoxLayout* layout = new QHBoxLayout; layout->addWidget(m_twPreview); ui.tePreview->setLayout(layout); m_twPreview->hide(); // default filter ui.swOptions->setCurrentIndex(AbstractFileFilter::Ascii); #if !defined(HAVE_HDF5) || !defined(HAVE_NETCDF) || !defined(HAVE_FITS) || !defined(HAVE_ZIP) const QStandardItemModel* model = qobject_cast(ui.cbFileType->model()); #endif #ifndef HAVE_HDF5 // disable HDF5 item QStandardItem* item = model->item(AbstractFileFilter::HDF5); item->setFlags(item->flags() & ~(Qt::ItemIsSelectable | Qt::ItemIsEnabled)); #endif #ifndef HAVE_NETCDF // disable NETCDF item QStandardItem* item2 = model->item(AbstractFileFilter::NETCDF); item2->setFlags(item2->flags() & ~(Qt::ItemIsSelectable | Qt::ItemIsEnabled)); #endif #ifndef HAVE_FITS // disable FITS item QStandardItem* item3 = model->item(AbstractFileFilter::FITS); item3->setFlags(item3->flags() & ~(Qt::ItemIsSelectable | Qt::ItemIsEnabled)); #endif #ifndef HAVE_ZIP // disable ROOT item QStandardItem* item4 = model->item(AbstractFileFilter::ROOT); item4->setFlags(item4->flags() & ~(Qt::ItemIsSelectable | Qt::ItemIsEnabled)); #endif #ifndef HAVE_MQTT // disable MQTT item QStandardItem* item5 = model->item(LiveDataSource::MQTT); item5->setFlags(item5->flags() & ~(Qt::ItemIsSelectable | Qt::ItemIsEnabled)); #endif ui.cbReadingType->addItem(i18n("Whole file"), LiveDataSource::WholeFile); ui.lePort->setValidator( new QIntValidator(ui.lePort) ); ui.gbOptions->hide(); ui.gbUpdateOptions->hide(); ui.bOpen->setIcon( QIcon::fromTheme("document-open") ); ui.bFileInfo->setIcon( QIcon::fromTheme("help-about") ); ui.bManageFilters->setIcon( QIcon::fromTheme("configure") ); ui.bSaveFilter->setIcon( QIcon::fromTheme("document-save") ); ui.bRefreshPreview->setIcon( QIcon::fromTheme("view-refresh") ); connect( ui.leFileName, SIGNAL(textChanged(QString)), SLOT(fileNameChanged(QString)) ); connect( ui.bOpen, SIGNAL(clicked()), this, SLOT (selectFile()) ); connect( ui.bFileInfo, SIGNAL(clicked()), this, SLOT (fileInfoDialog()) ); connect( ui.bSaveFilter, SIGNAL(clicked()), this, SLOT (saveFilter()) ); connect( ui.bManageFilters, SIGNAL(clicked()), this, SLOT (manageFilters()) ); connect( ui.cbFileType, SIGNAL(currentIndexChanged(int)), SLOT(fileTypeChanged(int)) ); connect( ui.cbUpdateType, SIGNAL(currentIndexChanged(int)), this, SLOT(updateTypeChanged(int))); connect( ui.cbReadingType, SIGNAL(currentIndexChanged(int)), this, SLOT(readingTypeChanged(int))); connect( ui.cbFilter, SIGNAL(activated(int)), SLOT(filterChanged(int)) ); connect( ui.bRefreshPreview, SIGNAL(clicked()), SLOT(refreshPreview()) ); #ifdef HAVE_MQTT connect(ui.chbID, &QCheckBox::stateChanged, this, &ImportFileWidget::idChecked); connect(ui.chbAuthentication, &QCheckBox::stateChanged, this, &ImportFileWidget::authenticationChecked); connect(ui.bConnect, &QPushButton::clicked, this, &ImportFileWidget::mqttConnection); connect(m_client, &QMqttClient::connected, this, &ImportFileWidget::onMqttConnect); connect(m_client, &QMqttClient::disconnected, this, &ImportFileWidget::onMqttDisconnect); connect(ui.bSubscribe, &QPushButton::clicked, this, &ImportFileWidget::mqttSubscribe); connect(ui.bUnsubscribe, &QPushButton::clicked, this,&ImportFileWidget::mqttUnsubscribe); connect(m_client, &QMqttClient::messageReceived, this, &ImportFileWidget::mqttMessageReceived); connect(this, &ImportFileWidget::newTopic, this, &ImportFileWidget::setTopicCompleter); connect(m_searchTimer, &QTimer::timeout, this, &ImportFileWidget::topicTimeout); connect(m_connectTimeoutTimer, &QTimer::timeout, this, &ImportFileWidget::mqttConnectTimeout); connect(ui.chbWill, &QCheckBox::stateChanged, this, &ImportFileWidget::useWillMessage); connect(ui.cbWillMessageType, static_cast(&QComboBox::currentIndexChanged), this, &ImportFileWidget::willMessageTypeChanged); connect(ui.cbWillUpdate, static_cast(&QComboBox::currentIndexChanged), this, &ImportFileWidget::willUpdateTypeChanged); connect(m_client, &QMqttClient::errorChanged, this, &ImportFileWidget::mqttErrorChanged); connect(ui.cbFileType, static_cast(&QComboBox::currentIndexChanged), [this]() {emit checkFileType();}); connect(ui.leTopics, &QLineEdit::textChanged, this, &ImportFileWidget::scrollToTopicTreeItem); connect(ui.leSubscriptions, &QLineEdit::textChanged, this, &ImportFileWidget::scrollToSubsriptionTreeItem); connect(ui.chbAuthentication, &QCheckBox::stateChanged, this, &ImportFileWidget::checkConnectEnable); connect(ui.chbID, &QCheckBox::stateChanged, this, &ImportFileWidget::checkConnectEnable); connect(ui.leHost, &QLineEdit::textChanged, this, &ImportFileWidget::checkConnectEnable); connect(ui.lePort, &QLineEdit::textChanged, this, &ImportFileWidget::checkConnectEnable); connect(ui.lePassword, &QLineEdit::textChanged, this, &ImportFileWidget::checkConnectEnable); connect(ui.leUsername, &QLineEdit::textChanged, this, &ImportFileWidget::checkConnectEnable); connect(ui.leID, &QLineEdit::textChanged, this, &ImportFileWidget::checkConnectEnable); ui.bSubscribe->setIcon(ui.bSubscribe->style()->standardIcon(QStyle::SP_ArrowRight)); ui.bUnsubscribe->setIcon(ui.bUnsubscribe->style()->standardIcon(QStyle::SP_BrowserStop)); #endif connect(ui.leHost, SIGNAL(textChanged(QString)), this, SIGNAL(hostChanged())); connect(ui.lePort, SIGNAL(textChanged(QString)), this, SIGNAL(portChanged())); connect( ui.cbSourceType, SIGNAL(currentIndexChanged(int)), this, SLOT(sourceTypeChanged(int))); connect( ui.tvJson, SIGNAL(clicked(const QModelIndex&)), this, SLOT(refreshPreview())); //TODO: implement save/load of user-defined settings later and activate these buttons again ui.bSaveFilter->hide(); ui.bManageFilters->hide(); //defer the loading of settings a bit in order to show the dialog prior to blocking the GUI in refreshPreview() QTimer::singleShot( 100, this, SLOT(loadSettings()) ); hideMQTT(); } void ImportFileWidget::loadSettings() { m_suppressRefresh = true; //load last used settings QString confName; if (m_liveDataSource) confName = QLatin1String("LiveDataImport"); else confName = QLatin1String("FileImport"); KConfigGroup conf(KSharedConfig::openConfig(), confName); //settings for data type specific widgets m_asciiOptionsWidget->loadSettings(); m_binaryOptionsWidget->loadSettings(); m_imageOptionsWidget->loadSettings(); m_jsonOptionsWidget->loadSettings(); //read the source type first since settings in fileNameChanged() depend on this ui.cbSourceType->setCurrentIndex(conf.readEntry("SourceType").toInt()); //general settings ui.cbFileType->setCurrentIndex(conf.readEntry("Type", 0)); ui.cbFilter->setCurrentIndex(conf.readEntry("Filter", 0)); filterChanged(ui.cbFilter->currentIndex()); // needed if filter is not changed if (m_fileName.isEmpty()) ui.leFileName->setText(conf.readEntry("LastImportedFile", "")); else ui.leFileName->setText(m_fileName); //live data related settings ui.cbBaudRate->setCurrentIndex(conf.readEntry("BaudRate").toInt()); ui.cbReadingType->setCurrentIndex(conf.readEntry("ReadingType").toInt()); ui.cbSerialPort->setCurrentIndex(conf.readEntry("SerialPort").toInt()); ui.cbUpdateType->setCurrentIndex(conf.readEntry("UpdateType").toInt()); ui.leHost->setText(conf.readEntry("Host","")); ui.sbKeepNValues->setValue(conf.readEntry("KeepNValues").toInt()); ui.lePort->setText(conf.readEntry("Port","")); ui.sbSampleSize->setValue(conf.readEntry("SampleSize").toInt()); ui.sbUpdateInterval->setValue(conf.readEntry("UpdateInterval").toInt()); #ifdef HAVE_MQTT //MQTT related settings ui.chbID->setChecked(conf.readEntry("mqttUseId").toInt()); ui.chbAuthentication->setChecked(conf.readEntry("mqttUseAuthentication").toInt()); ui.chbRetain->setChecked(conf.readEntry("mqttUseRetain").toInt()); ui.leUsername->setText(conf.readEntry("mqttUsername","")); ui.lePassword->setText(conf.readEntry("mqttPassword","")); ui.leID->setText(conf.readEntry("mqttId","")); ui.chbWillRetain->setChecked(conf.readEntry("mqttWillRetain").toInt()); ui.cbWillUpdate->setCurrentIndex(conf.readEntry("mqttWillUpdateType").toInt()); ui.cbWillQoS->setCurrentIndex(conf.readEntry("mqttWillQoS").toInt()); ui.leWillOwnMessage->setText(conf.readEntry("mqttWillOwnMessage","")); ui.leWillUpdateInterval->setText(conf.readEntry("mqttWillUpdateInterval","")); QString willStatistics = conf.readEntry("mqttWillStatistics",""); QStringList statisticsList = willStatistics.split('|', QString::SplitBehavior::SkipEmptyParts); for(auto value : statisticsList) { QListWidgetItem* item = ui.lwWillStatistics->item(value.toInt()); item->setCheckState(Qt::Checked); } ui.cbWillMessageType->setCurrentIndex(conf.readEntry("mqttWillMessageType").toInt()); ui.chbWill->setChecked(conf.readEntry("mqttWillUse").toInt()); //chbWill is unchecked by deafult, so if false is loaded it doesn't emit state changed signal, we have to force it if(!ui.chbWill->isChecked()) { ui.chbWill->setChecked(true); ui.chbWill->setChecked(false); } #endif m_suppressRefresh = false; refreshPreview(); } ImportFileWidget::~ImportFileWidget() { // save current settings QString confName; if (m_liveDataSource) confName = QLatin1String("LiveDataImport"); else confName = QLatin1String("FileImport"); KConfigGroup conf(KSharedConfig::openConfig(), confName); // general settings conf.writeEntry("Type", ui.cbFileType->currentIndex()); conf.writeEntry("Filter", ui.cbFilter->currentIndex()); conf.writeEntry("LastImportedFile", ui.leFileName->text()); //live data related settings conf.writeEntry("SourceType", ui.cbSourceType->currentIndex()); conf.writeEntry("UpdateType", ui.cbUpdateType->currentIndex()); conf.writeEntry("ReadingType", ui.cbReadingType->currentIndex()); conf.writeEntry("SampleSize", ui.sbSampleSize->value()); conf.writeEntry("KeepNValues", ui.sbKeepNValues->value()); conf.writeEntry("BaudRate", ui.cbBaudRate->currentIndex()); conf.writeEntry("SerialPort", ui.cbSerialPort->currentIndex()); conf.writeEntry("Host", ui.leHost->text()); conf.writeEntry("Port", ui.lePort->text()); conf.writeEntry("UpdateInterval", ui.sbUpdateInterval->value()); #ifdef HAVE_MQTT //MQTT related settings conf.writeEntry("mqttUsername", ui.leUsername->text()); conf.writeEntry("mqttPassword", ui.lePassword->text()); conf.writeEntry("mqttId", ui.leID->text()); conf.writeEntry("mqttWillMessageType", ui.cbWillMessageType->currentIndex()); conf.writeEntry("mqttWillUpdateType", ui.cbWillUpdate->currentIndex()); conf.writeEntry("mqttWillQoS", ui.cbWillQoS->currentIndex()); conf.writeEntry("mqttWillOwnMessage", ui.leWillOwnMessage->text()); conf.writeEntry("mqttWillUpdateInterval", ui.leWillUpdateInterval->text()); QString willStatistics; for(int i = 0; i < ui.lwWillStatistics->count(); ++i) { QListWidgetItem* item = ui.lwWillStatistics->item(i); if (item->checkState() == Qt::Checked) willStatistics += QString::number(i)+"|"; } conf.writeEntry("mqttWillStatistics", willStatistics); conf.writeEntry("mqttWillRetain", static_cast(ui.chbWillRetain->isChecked())); conf.writeEntry("mqttWillUse", static_cast(ui.chbWill->isChecked())); conf.writeEntry("mqttUseId", static_cast(ui.chbID->isChecked())); conf.writeEntry("mqttUseAuthentication", static_cast(ui.chbAuthentication->isChecked())); conf.writeEntry("mqttUseRetain", static_cast(ui.chbRetain->isChecked())); #endif // data type specific settings m_asciiOptionsWidget->saveSettings(); m_binaryOptionsWidget->saveSettings(); m_imageOptionsWidget->saveSettings(); m_jsonOptionsWidget->saveSettings(); } void ImportFileWidget::hideDataSource() { m_liveDataSource = false; ui.gbUpdateOptions->hide(); ui.chbLinkFile->hide(); ui.cbBaudRate->hide(); ui.lBaudRate->hide(); ui.lHost->hide(); ui.leHost->hide(); ui.lPort->hide(); ui.lePort->hide(); ui.cbSerialPort->hide(); ui.lSerialPort->hide(); ui.lSourceType->hide(); ui.cbSourceType->hide(); ui.cbUpdateType->hide(); ui.lUpdateType->hide(); ui.sbUpdateInterval->hide(); ui.lUpdateInterval->hide(); #ifdef HAVE_MQTT hideMQTT(); #endif } void ImportFileWidget::showAsciiHeaderOptions(bool b) { m_asciiOptionsWidget->showAsciiHeaderOptions(b); } void ImportFileWidget::showJsonModel(bool b) { ui.tvJson->setVisible(b); ui.lField->setVisible(b); } void ImportFileWidget::showOptions(bool b) { ui.gbOptions->setVisible(b); if (m_liveDataSource) ui.gbUpdateOptions->setVisible(b); resize(layout()->minimumSize()); } QString ImportFileWidget::fileName() const { return ui.leFileName->text(); } QString ImportFileWidget::selectedObject() const { const QString& path = ui.leFileName->text(); //determine the file name only QString name = path.right(path.length() - path.lastIndexOf(QDir::separator()) - 1); //strip away the extension if available if (name.indexOf('.') != -1) name = name.left(name.lastIndexOf('.')); //for multi-dimensinal formats like HDF, netCDF and FITS add the currently selected object const auto format = currentFileType(); if (format == AbstractFileFilter::HDF5) { const QStringList& hdf5Names = m_hdf5OptionsWidget->selectedHDF5Names(); if (hdf5Names.size()) name += hdf5Names.first(); //the names of the selected HDF5 objects already have '/' } else if (format == AbstractFileFilter::NETCDF) { const QStringList& names = m_netcdfOptionsWidget->selectedNetCDFNames(); if (names.size()) name += QLatin1Char('/') + names.first(); } else if (format == AbstractFileFilter::FITS) { const QString& extensionName = m_fitsOptionsWidget->currentExtensionName(); if (!extensionName.isEmpty()) name += QLatin1Char('/') + extensionName; } else if (format == AbstractFileFilter::ROOT) { const QStringList& names = m_rootOptionsWidget->selectedROOTNames(); if (names.size()) name += QLatin1Char('/') + names.first(); } return name; } /*! * returns \c true if the number of lines to be imported from the currently selected file is zero ("file is empty"), * returns \c false otherwise. */ bool ImportFileWidget::isFileEmpty() const { return m_fileEmpty; } QString ImportFileWidget::host() const { return ui.leHost->text(); } QString ImportFileWidget::port() const { return ui.lePort->text(); } QString ImportFileWidget::serialPort() const { return ui.cbSerialPort->currentText(); } int ImportFileWidget::baudRate() const { return ui.cbBaudRate->currentText().toInt(); } /*! saves the settings to the data source \c source. */ void ImportFileWidget::saveSettings(LiveDataSource* source) const { AbstractFileFilter::FileType fileType = static_cast(ui.cbFileType->currentIndex()); LiveDataSource::UpdateType updateType = static_cast(ui.cbUpdateType->currentIndex()); LiveDataSource::SourceType sourceType = static_cast(ui.cbSourceType->currentIndex()); LiveDataSource::ReadingType readingType = static_cast(ui.cbReadingType->currentIndex()); source->setComment( ui.leFileName->text() ); source->setFileType(fileType); source->setFilter(this->currentFileFilter()); source->setSourceType(sourceType); source->setReadingType(readingType); if (updateType == LiveDataSource::UpdateType::TimeInterval) source->setUpdateInterval(ui.sbUpdateInterval->value()); else source->setFileWatched(true); source->setKeepNValues(ui.sbKeepNValues->value()); source->setUpdateType(updateType); if (readingType != LiveDataSource::ReadingType::TillEnd) source->setSampleSize(ui.sbSampleSize->value()); switch (sourceType) { case LiveDataSource::SourceType::FileOrPipe: source->setFileName(ui.leFileName->text()); source->setFileLinked(ui.chbLinkFile->isChecked()); break; case LiveDataSource::SourceType::LocalSocket: source->setFileName(ui.leFileName->text()); source->setLocalSocketName(ui.leFileName->text()); break; case LiveDataSource::SourceType::NetworkTcpSocket: case LiveDataSource::SourceType::NetworkUdpSocket: source->setHost(ui.leHost->text()); source->setPort((quint16)ui.lePort->text().toInt()); break; case LiveDataSource::SourceType::SerialPort: source->setBaudRate(ui.cbBaudRate->currentText().toInt()); source->setSerialPort(ui.cbSerialPort->currentText()); break; default: break; } } #ifdef HAVE_MQTT /*! saves the settings to the MQTTClient \c client. */ void ImportFileWidget::saveMQTTSettings(MQTTClient* client) const { MQTTClient::UpdateType updateType = static_cast(ui.cbUpdateType->currentIndex()); MQTTClient::ReadingType readingType = static_cast(ui.cbReadingType->currentIndex()); client->setComment( ui.leFileName->text() ); client->setFilter(this->currentFileFilter()); client->setReadingType(readingType); if (updateType == MQTTClient::UpdateType::TimeInterval) client->setUpdateInterval(ui.sbUpdateInterval->value()); client->setKeepNValues(ui.sbKeepNValues->value()); client->setUpdateType(updateType); if (readingType != MQTTClient::ReadingType::TillEnd) client->setSampleSize(ui.sbSampleSize->value()); - client->setMqttClientHostPort(m_client->hostname(), m_client->port()); + client->setMQTTClientHostPort(m_client->hostname(), m_client->port()); client->setMQTTUseAuthentication(ui.chbAuthentication->isChecked()); if(ui.chbAuthentication->isChecked()) - client->setMqttClientAuthentication(m_client->username(), m_client->password()); + client->setMQTTClientAuthentication(m_client->username(), m_client->password()); client->setMQTTUseID(ui.chbID->isChecked()); if(ui.chbID->isChecked()) - client->setMqttClientId(m_client->clientId()); + client->setMQTTClientId(m_client->clientId()); for(int i=0; iaddInitialMqttSubscriptions(m_mqttSubscriptions[i]->topic(), m_mqttSubscriptions[i]->qos()); + client->addInitialMQTTSubscriptions(m_mqttSubscriptions[i]->topic(), m_mqttSubscriptions[i]->qos()); } - client->setMqttRetain(ui.chbRetain->isChecked()); + client->setMQTTRetain(ui.chbRetain->isChecked()); client->setWillMessageType(static_cast(ui.cbWillMessageType->currentIndex()) ); client->setWillOwnMessage(ui.leWillOwnMessage->text()); client->setWillQoS(ui.cbWillQoS->currentIndex() ); client->setWillRetain(ui.chbWillRetain->isChecked()); client->setWillTimeInterval(ui.leWillUpdateInterval->text().toInt()); client->setWillTopic(ui.cbWillTopic->currentText()); client->setWillUpdateType(static_cast(ui.cbWillUpdate->currentIndex()) ); - client->setMqttWillUse(ui.chbWill->isChecked()); + client->setMQTTWillUse(ui.chbWill->isChecked()); for(int i = 0; i < ui.lwWillStatistics->count(); ++i) { QListWidgetItem* item = ui.lwWillStatistics->item(i); if (item->checkState() == Qt::Checked) client->addWillStatistics(static_cast (i)); } } #endif /*! returns the currently used file type. */ AbstractFileFilter::FileType ImportFileWidget::currentFileType() const { return static_cast(ui.cbFileType->currentIndex()); } LiveDataSource::SourceType ImportFileWidget::currentSourceType() const { return static_cast(ui.cbSourceType->currentIndex()); } /*! returns the currently used filter. */ AbstractFileFilter* ImportFileWidget::currentFileFilter() const { DEBUG("ImportFileWidget::currentFileFilter()"); AbstractFileFilter::FileType fileType = static_cast(ui.cbFileType->currentIndex()); switch (fileType) { case AbstractFileFilter::Ascii: { DEBUG(" ASCII"); //TODO std::unique_ptr filter(new AsciiFilter()); AsciiFilter* filter = new AsciiFilter(); if (ui.cbFilter->currentIndex() == 0) //"automatic" filter->setAutoModeEnabled(true); else if (ui.cbFilter->currentIndex() == 1) { //"custom" filter->setAutoModeEnabled(false); m_asciiOptionsWidget->applyFilterSettings(filter); } else filter->loadFilterSettings( ui.cbFilter->currentText() ); //save the data portion to import filter->setStartRow( ui.sbStartRow->value()); filter->setEndRow( ui.sbEndRow->value() ); filter->setStartColumn( ui.sbStartColumn->value()); filter->setEndColumn( ui.sbEndColumn->value()); return filter; } case AbstractFileFilter::Binary: { BinaryFilter* filter = new BinaryFilter(); if ( ui.cbFilter->currentIndex() == 0 ) //"automatic" filter->setAutoModeEnabled(true); else if ( ui.cbFilter->currentIndex() == 1 ) { //"custom" filter->setAutoModeEnabled(false); m_binaryOptionsWidget->applyFilterSettings(filter); } else { //TODO: load filter settings // filter->setFilterName( ui.cbFilter->currentText() ); } filter->setStartRow( ui.sbStartRow->value() ); filter->setEndRow( ui.sbEndRow->value() ); return filter; } case AbstractFileFilter::Image: { ImageFilter* filter = new ImageFilter(); filter->setImportFormat(m_imageOptionsWidget->currentFormat()); filter->setStartRow( ui.sbStartRow->value() ); filter->setEndRow( ui.sbEndRow->value() ); filter->setStartColumn( ui.sbStartColumn->value() ); filter->setEndColumn( ui.sbEndColumn->value() ); return filter; } case AbstractFileFilter::HDF5: { HDF5Filter* filter = new HDF5Filter(); QStringList names = selectedHDF5Names(); if (!names.isEmpty()) filter->setCurrentDataSetName(names[0]); filter->setStartRow( ui.sbStartRow->value() ); filter->setEndRow( ui.sbEndRow->value() ); filter->setStartColumn( ui.sbStartColumn->value() ); filter->setEndColumn( ui.sbEndColumn->value() ); return filter; } case AbstractFileFilter::NETCDF: { NetCDFFilter* filter = new NetCDFFilter(); if (!selectedNetCDFNames().isEmpty()) filter->setCurrentVarName(selectedNetCDFNames()[0]); filter->setStartRow( ui.sbStartRow->value() ); filter->setEndRow( ui.sbEndRow->value() ); filter->setStartColumn( ui.sbStartColumn->value() ); filter->setEndColumn( ui.sbEndColumn->value() ); return filter; } case AbstractFileFilter::FITS: { FITSFilter* filter = new FITSFilter(); filter->setStartRow( ui.sbStartRow->value()); filter->setEndRow( ui.sbEndRow->value() ); filter->setStartColumn( ui.sbStartColumn->value()); filter->setEndColumn( ui.sbEndColumn->value()); return filter; } case AbstractFileFilter::Json: { JsonFilter* filter = new JsonFilter(); m_jsonOptionsWidget->applyFilterSettings(filter, ui.tvJson->currentIndex()); filter->setStartRow( ui.sbStartRow->value() ); filter->setEndRow( ui.sbEndRow->value() ); filter->setStartColumn( ui.sbStartColumn->value()); filter->setEndColumn( ui.sbEndColumn->value()); return filter; } case AbstractFileFilter::ROOT: { ROOTFilter* filter = new ROOTFilter(); QStringList names = selectedROOTNames(); if (!names.isEmpty()) filter->setCurrentHistogram(names.first()); filter->setStartBin( m_rootOptionsWidget->startBin() ); filter->setEndBin( m_rootOptionsWidget->endBin() ); filter->setColumns( m_rootOptionsWidget->columns() ); return filter; } case AbstractFileFilter::NgspiceRawAscii: { NgspiceRawAsciiFilter* filter = new NgspiceRawAsciiFilter(); filter->setStartRow( ui.sbStartRow->value() ); filter->setEndRow( ui.sbEndRow->value() ); return filter; } case AbstractFileFilter::NgspiceRawBinary: { NgspiceRawBinaryFilter* filter = new NgspiceRawBinaryFilter(); filter->setStartRow( ui.sbStartRow->value() ); filter->setEndRow( ui.sbEndRow->value() ); return filter; } } return 0; } /*! opens a file dialog and lets the user select the file data source. */ void ImportFileWidget::selectFile() { KConfigGroup conf(KSharedConfig::openConfig(), "ImportFileWidget"); QString dir = conf.readEntry("LastDir", ""); QString path = QFileDialog::getOpenFileName(this, i18n("Select the File Data Source"), dir); if (path.isEmpty()) //cancel was clicked in the file-dialog return; int pos = path.lastIndexOf(QDir::separator()); if (pos != -1) { QString newDir = path.left(pos); if (newDir != dir) conf.writeEntry("LastDir", newDir); } ui.leFileName->setText(path); //TODO: decide whether the selection of several files should be possible // QStringList filelist = QFileDialog::getOpenFileNames(this,i18n("Select one or more files to open")); // if (! filelist.isEmpty() ) // ui.leFileName->setText(filelist.join(";")); } /*! hides the MQTT related items of the widget */ void ImportFileWidget::hideMQTT() { ui.leID->hide(); ui.lMqttID->hide(); ui.lePassword->hide(); ui.lPassword->hide(); ui.leUsername->hide(); ui.lUsername->hide(); ui.cbQos->hide(); ui.lQos->hide(); ui.twTopics->hide(); ui.lTopicSearch->hide(); ui.leTopics->hide(); ui.twSubscriptions->hide(); ui.chbAuthentication->hide(); ui.chbID->hide(); ui.bSubscribe->hide(); ui.bUnsubscribe->hide(); ui.bConnect->hide(); ui.gbMqttWill->hide(); ui.chbWill->hide(); ui.chbWillRetain->hide(); ui.cbWillQoS->hide(); ui.cbWillMessageType->hide(); ui.cbWillTopic->hide(); ui.cbWillUpdate->hide(); ui.leWillOwnMessage->hide(); ui.leWillUpdateInterval->setValidator(new QIntValidator(2, 1000000) ); ui.leWillUpdateInterval->hide(); ui.lWillMessageType->hide(); ui.lWillOwnMessage->hide(); ui.lWillQos->hide(); ui.lWillTopic->hide(); ui.lWillUpdate->hide(); ui.lWillUpdateInterval->hide(); ui.lwWillStatistics->hide(); ui.lWillStatistics->hide(); } #ifdef HAVE_MQTT /*! * returns \c true if there is a valid connection to an MQTT broker and the user has subscribed to at least 1 topic, * returns \c false otherwise. */ bool ImportFileWidget::isMqttValid(){ bool connected = (m_client->state() == QMqttClient::ClientState::Connected); bool subscribed = (ui.twSubscriptions->topLevelItemCount() > 0); bool fileTypeOk = false; if(this->currentFileType() == AbstractFileFilter::FileType::Ascii) fileTypeOk = true; return connected && subscribed && fileTypeOk; } /*! *\brief Checks if a topic contains another one * * \param superior the name of a topic * \param inferior the name of a topic * \return true if superior is equal to or contains(if superior contains wildcards) inferior, * false otherwise */ bool ImportFileWidget::checkTopicContains(const QString& superior, const QString& inferior) { if (superior == inferior) return true; else { if(superior.contains("/")) { QStringList superiorList = superior.split('/', QString::SkipEmptyParts); QStringList inferiorList = inferior.split('/', QString::SkipEmptyParts); //a longer topic can't contain a shorter one if(superiorList.size() > inferiorList.size()) return false; bool ok = true; for(int i = 0; i < superiorList.size(); ++i) { if(superiorList.at(i) != inferiorList.at(i)) { if((superiorList.at(i) != "+") && !(superiorList.at(i) == "#" && i == superiorList.size() - 1)) { //if the two topics differ, and the superior's current level isn't + or #(which can be only in the last position) //then superior can't contain inferior qDebug() < 0) { for(int j = differIndex + 1; j < firstList.size(); ++j) { if(firstList.at(j) != secondtList.at(j)) { differ = true; break; } } } else differ = true; if(!differ) { for(int i = 0; i < firstList.size(); ++i) { if(i != differIndex) { commonTopic.append(firstList.at(i)); } else { //we put "+" wildcard at the level where they differ commonTopic.append("+"); } if(i != firstList.size() - 1) commonTopic.append("/"); } } } } qDebug() << "Common topic: "< 0) { for(int j = differIndex + 1; j < firstList.size(); ++j) { if(firstList.at(j) != secondtList.at(j)) { differ = true; break; } } } else differ = true; if(!differ) { for(int i = 0; i < firstList.size(); ++i) { if(i != differIndex) commonTopic.append(firstList.at(i)); else commonTopic.append("+"); if(i != firstList.size() - 1) commonTopic.append("/"); } } } } qDebug() << "Common topic: "<unsubscribe(filter); qDebug()<<"unsubscribe occured"; for(int i = 0; i< m_mqttSubscriptions.count(); ++i) if(m_mqttSubscriptions[i]->topic().filter() == topicName) { m_mqttSubscriptions.remove(i); break; } m_mqttReadyForPreview = false; QMapIterator i(m_messageArrived); while(i.hasNext()) { i.next(); if(checkTopicContains(topicName, i.key().name())) { m_messageArrived.remove(i.key()); } } QMapIterator j(m_lastMessage); while(j.hasNext()) { j.next(); if(checkTopicContains(topicName, j.key().name())) { m_lastMessage.remove(j.key()); } } for(int row = 0; rowtopLevelItemCount(); row++) { if(ui.twSubscriptions->topLevelItem(row)->text(0) == topicName) { ui.twSubscriptions->topLevelItem(row)->takeChildren(); ui.twSubscriptions->takeTopLevelItem(row); } } for(int i = 0; i < m_subscribedTopicNames.size(); ++i) { if(checkTopicContains(topicName, m_subscribedTopicNames[i])) { m_subscribedTopicNames.remove(i); i--; } } for(int item = 0; item < ui.cbWillTopic->count(); ++item) { if(checkTopicContains(topicName, ui.cbWillTopic->itemText(item))) { ui.cbWillTopic->removeItem(item); item--; } } //signals that there was a change among the subscribed topics emit subscriptionsChanged(); refreshPreview(); } } /*! *\brief Adds to a # wildcard containing topic, every topic present in twTopics that the former topic contains * * \param topic pointer to the TreeWidgetItem which was selected before subscribing * \param subscription pointer to the TreeWidgetItem which represents the new subscirption, * we add all of the children to this item */ void ImportFileWidget::addSubscriptionChildren(QTreeWidgetItem * topic, QTreeWidgetItem * subscription) { //if the topic doesn't have any children we don't do anything if(topic->childCount() > 0) { for(int i = 0; i < topic->childCount(); ++i) { QTreeWidgetItem* temp = topic->child(i); QString name; //if it has children, then we add it as a # wildcrad containing topic if(topic->child(i)->childCount() > 0) { name.append(temp->text(0) + "/#"); while(temp->parent() != nullptr) { temp = temp->parent(); name.prepend(temp->text(0) + "/"); } } //if not then we simply add the topic itself else { name.append(temp->text(0)); while(temp->parent() != nullptr) { temp = temp->parent(); name.prepend(temp->text(0) + "/"); } } QStringList nameList; nameList.append(name); QTreeWidgetItem* childItem = new QTreeWidgetItem(nameList); subscription->addChild(childItem); //we use the function recursively on the given item addSubscriptionChildren(topic->child(i), childItem); } } } /*! *\brief Fills the children vector, with the root item's (twSubscriptions) leaf children (meaning no wildcard containing topics) * * \param children vector of TreeWidgetItem pointers * \param root pointer to a TreeWidgetItem of twSubscriptions */ void ImportFileWidget::findSubscriptionLeafChildren(QVector& children, QTreeWidgetItem* root) { if(root->childCount() == 0) { children.push_back(root); } else { for(int i = 0; i < root->childCount(); ++i) { findSubscriptionLeafChildren(children, root->child(i)); } } } /*! *\brief Returns the amount of topics that the "+" wildcard will replace in the level position * * \param levelIdx the level currently being investigated * \param level the level where the new + wildcard will be placed * \param commonList the topic name split into levels * \param currentItem pointer to a TreeWidgetItem which represents the parent of the level * represented by levelIdx * \return returns the childCount, or -1 if some topics already represented by + wildcard have different * amount of children */ int ImportFileWidget::checkCommonChildCount(int levelIdx, int level, QStringList& commonList, QTreeWidgetItem* currentItem) { //we recursively check the number of children, until we get to level-1 if(levelIdx < level - 1) { if(commonList[levelIdx] != "+") { for(int j = 0; j < currentItem->childCount(); ++j) { if(currentItem->child(j)->text(0) == commonList[levelIdx]) { //if the level isn't represented by + wildcard we simply return the amount of children of the corresponding item, recursively return checkCommonChildCount(levelIdx + 1, level, commonList, currentItem->child(j)); } } } else { int childCount = -1; bool ok = true; //otherwise we check if every + wildcard represented topic has the same number of children, recursively for(int j = 0; j < currentItem->childCount(); ++j) { int temp = checkCommonChildCount(levelIdx + 1, level, commonList, currentItem->child(j)); if((j > 0) && (temp != childCount)) { ok = false; break; } childCount = temp; } //if yes we return this number, otherwise -1 if(ok) return childCount; else return -1; } } else if (levelIdx == level - 1) { if(commonList[levelIdx] != "+") { for(int j = 0; j < currentItem->childCount(); ++j) { if(currentItem->child(j)->text(0) == commonList[levelIdx]) { //if the level isn't represented by + wildcard we simply return the amount of children of the corresponding item return currentItem->child(j)->childCount(); } } } else { int childCount = -1; bool ok = true; //otherwise we check if every + wildcard represented topic has the same number of children for(int j = 0; j < currentItem->childCount(); ++j) { if((j > 0) && (currentItem->child(j)->childCount() != childCount)) { ok = false; break; } childCount = currentItem->child(j)->childCount(); } //if yes we return this number, otherwise -1 if(ok) return childCount; else return -1; } } else if (level == 1 && levelIdx == 1) { return currentItem->childCount(); } return -1; } /*! *\brief We search in twSubscriptions for topics that can be represented using + wildcards, then merge them. * We do this until there are no topics to merge */ void ImportFileWidget::manageCommonLevelSubscriptions() { bool foundEqual = false; do{ foundEqual = false; QMap> equalTopicsMap; QVector equalTopics; //compare the subscriptions present in the TreeWidget for(int i = 0; i < ui.twSubscriptions->topLevelItemCount() - 1; ++i) { for(int j = i + 1; j < ui.twSubscriptions->topLevelItemCount(); ++j) { qDebug()<topLevelItem(i)->text(0)<<" "<topLevelItem(j)->text(0); QString commonTopic = checkCommonLevel(ui.twSubscriptions->topLevelItem(i)->text(0), ui.twSubscriptions->topLevelItem(j)->text(0)); //if there is a common topic for the 2 compared topics, we add them to the map (using the common topic as key) if(!commonTopic.isEmpty()) { if(!equalTopicsMap[commonTopic].contains(ui.twSubscriptions->topLevelItem(i)->text(0))) { equalTopicsMap[commonTopic].push_back(ui.twSubscriptions->topLevelItem(i)->text(0)); } if(!equalTopicsMap[commonTopic].contains(ui.twSubscriptions->topLevelItem(j)->text(0))) { qDebug()<topLevelItem(i)->text(0); equalTopicsMap[commonTopic].push_back(ui.twSubscriptions->topLevelItem(j)->text(0)); } } } } if(!equalTopicsMap.isEmpty()) { qDebug()<<"Equal topics not empty"; QVector commonTopics; QMapIterator> topics(equalTopicsMap); //check for every map entry, if the found topics can be merged or not while(topics.hasNext()) { topics.next(); int level = commonLevelIndex(topics.value().last(), topics.value().first()); QStringList commonList = topics.value().first().split('/', QString::SkipEmptyParts); QTreeWidgetItem* currentItem; //search the corresponding item to the common topics first level(root) for(int i = 0; i < ui.twTopics->topLevelItemCount(); ++i) { if(ui.twTopics->topLevelItem(i)->text(0) == commonList.first()) { currentItem = ui.twTopics->topLevelItem(i); break; } } //calculate the number of topics the new + wildcard could replace int childCount = checkCommonChildCount(1, level, commonList, currentItem); if(childCount > 0) { //if the number of topics found and the calculated number of topics is equal, the topics can be merged if(topics.value().size() == childCount) { foundEqual = true; commonTopics.push_back(topics.key()); } } } if(foundEqual) { //if there are more common topics, the topics of which can be merged, we choose the one which has the lowest level new "+" wildcard int lowestLevel = INT_MAX; int topicIdx = -1; for(int i = 0; i < commonTopics.size(); ++i) { int level = commonLevelIndex(equalTopicsMap[commonTopics[i]].first(), commonTopics[i]); if(level < lowestLevel) { topicIdx = i; lowestLevel = level; } } equalTopics.append(equalTopicsMap[commonTopics[topicIdx]]); qDebug()<<"Adding common topic"; //Add the common topic ("merging") QString commonTopic; commonTopic = checkCommonLevel(equalTopics.first(), equalTopics.last()); QStringList nameList; nameList.append(commonTopic); QTreeWidgetItem* newTopic = new QTreeWidgetItem(nameList); ui.twSubscriptions->addTopLevelItem(newTopic); QMqttTopicFilter filter {commonTopic}; QMqttSubscription *temp_subscription = m_client->subscribe(filter, static_cast (ui.cbQos->currentText().toUInt()) ); if(temp_subscription) { m_mqttSubscriptions.push_back(temp_subscription); connect(temp_subscription, &QMqttSubscription::messageReceived, this, &ImportFileWidget::mqttSubscriptionMessageReceived); emit subscriptionsChanged(); } //remove the "merged" topics qDebug()<<"unsubscribe from equal topics"; for(int i = 0; i < equalTopics.size(); ++i) { for(int j = 0; j < ui.twSubscriptions->topLevelItemCount(); ++j){ if(ui.twSubscriptions->topLevelItem(j)->text(0) == equalTopics[i]) { newTopic->addChild(ui.twSubscriptions->takeTopLevelItem(j)); unsubscribeFromTopic(equalTopics[i]); break; } } } //remove any subscription that the new subscription contains for(int i = 0; i < ui.twSubscriptions->topLevelItemCount(); ++i) { if(checkTopicContains(commonTopic, ui.twSubscriptions->topLevelItem(i)->text(0)) && commonTopic != ui.twSubscriptions->topLevelItem(i)->text(0) ) { unsubscribeFromTopic(ui.twSubscriptions->topLevelItem(i)->text(0)); i--; } } } } } while(foundEqual); } /*! *\brief Fills twSubscriptions with the subscriptions made by the client */ void ImportFileWidget::updateSubscriptionTree() { ui.twSubscriptions->clear(); for (int i = 0; i < m_mqttSubscriptions.size(); ++i) { QStringList name; name.append(m_mqttSubscriptions[i]->topic().filter()); bool found = false; for(int j = 0; j < ui.twSubscriptions->topLevelItemCount(); ++j) { if(ui.twSubscriptions->topLevelItem(j)->text(0) == m_mqttSubscriptions[i]->topic().filter()) { found = true; break; } } if(!found) { qDebug()<<"add:" << m_mqttSubscriptions[i]->topic().filter(); //Add the subscription to the tree widget QTreeWidgetItem* newItem = new QTreeWidgetItem(name); ui.twSubscriptions->addTopLevelItem(newItem); name.clear(); name = m_mqttSubscriptions[i]->topic().filter().split('/', QString::SkipEmptyParts); //find the corresponding "root" item in twTopics QTreeWidgetItem* topic = nullptr; for(int j = 0; j < ui.twTopics->topLevelItemCount(); ++j) { if(ui.twTopics->topLevelItem(j)->text(0) == name[0]) { qDebug()<<"found top level topic: "<topLevelItem(j); break; } } //restore the children of the subscription if(topic != nullptr && topic->childCount() > 0) { qDebug()<<"restoring Children"; restoreSubscriptionChildren(topic, newItem, name, 1); } } } m_searching = false; updateWillTopics(); } /*! *\brief Restores the children of a top level item in twSubscriptions if it contains wildcards * * \param topic pointer to a top level item in twTopics wich represents the root of the subscription topic * \param subscription pointer to a top level item in twSubscriptions, this is the item whose children will be restored * \param list QStringList containing the levels of the subscription topic * \param level the level's number which is being investigated */ void ImportFileWidget::restoreSubscriptionChildren(QTreeWidgetItem * topic, QTreeWidgetItem * subscription, const QStringList& list, int level) { if(list[level] != "+" && list[level] != "#" && level < list.size() - 1) { for(int i = 0; i < topic->childCount(); ++i) { //if the current level isn't + or # wildcard we recursively continue with the next level if(topic->child(i)->text(0) == list[level]) { restoreSubscriptionChildren(topic->child(i), subscription, list, level + 1); break; } } } else if (list[level] == "+") { for(int i = 0; i < topic->childCount(); ++i) { //determine the name of the topic, contained by the subscription QString name; name.append(topic->child(i)->text(0)); for(int j = level + 1; j < list.size(); ++j) { name.append("/" + list[j]); } QTreeWidgetItem* temp = topic->child(i); while(temp->parent() != nullptr) { temp = temp->parent(); name.prepend(temp->text(0) + "/"); } //Add the topic as child of the subscription QStringList nameList; nameList.append(name); QTreeWidgetItem* newItem = new QTreeWidgetItem(nameList); subscription->addChild(newItem); //Continue adding children recursively to the new item restoreSubscriptionChildren(topic->child(i), newItem, list, level + 1); } } else if (list[level] == "#") { //add the children of the # wildcard containing subscription addSubscriptionChildren(topic, subscription); } } /*! *\brief Updates the completer for leSubscriptions */ void ImportFileWidget::updateSubscriptionCompleter() { QStringList subscriptionList; for(int i = 0; i < ui.twSubscriptions->topLevelItemCount(); ++i) { subscriptionList.append(ui.twSubscriptions->topLevelItem(i)->text(0)); } if(!subscriptionList.isEmpty()) { m_subscriptionCompleter = new QCompleter(subscriptionList, this); m_subscriptionCompleter->setCompletionMode(QCompleter::PopupCompletion); m_subscriptionCompleter->setCaseSensitivity(Qt::CaseSensitive); ui.leSubscriptions->setCompleter(m_subscriptionCompleter); } else { ui.leSubscriptions->setCompleter(0); } } #endif /************** SLOTS **************************************************************/ /*! called on file name changes. Determines the file format (ASCII, binary etc.), if the file exists, and activates the corresponding options. */ void ImportFileWidget::fileNameChanged(const QString& name) { QString fileName = name; #ifndef HAVE_WINDOWS // make relative path if ( !fileName.isEmpty() && fileName.at(0) != QDir::separator()) fileName = QDir::homePath() + QDir::separator() + fileName; #endif bool fileExists = QFile::exists(fileName); if (fileExists) ui.leFileName->setStyleSheet(""); else ui.leFileName->setStyleSheet("QLineEdit{background:red;}"); ui.gbOptions->setEnabled(fileExists); ui.bManageFilters->setEnabled(fileExists); ui.cbFilter->setEnabled(fileExists); ui.cbFileType->setEnabled(fileExists); ui.bFileInfo->setEnabled(fileExists); ui.gbUpdateOptions->setEnabled(fileExists); if (!fileExists) { //file doesn't exist -> delete the content preview that is still potentially //available from the previously selected file ui.tePreview->clear(); m_twPreview->clear(); m_hdf5OptionsWidget->clear(); m_netcdfOptionsWidget->clear(); m_fitsOptionsWidget->clear(); m_jsonOptionsWidget->clearModel(); m_rootOptionsWidget->clear(); emit fileNameChanged(); return; } if (currentSourceType() == LiveDataSource::FileOrPipe) { const AbstractFileFilter::FileType fileType = AbstractFileFilter::fileType(fileName); switch(fileType) { case AbstractFileFilter::Ascii: ui.cbFileType->setCurrentIndex(AbstractFileFilter::Ascii); break; case AbstractFileFilter::Binary: ui.cbFileType->setCurrentIndex(AbstractFileFilter::Binary); break; case AbstractFileFilter::Image: ui.cbFileType->setCurrentIndex(AbstractFileFilter::Image); break; case AbstractFileFilter::HDF5: ui.cbFileType->setCurrentIndex(AbstractFileFilter::HDF5); m_hdf5OptionsWidget->updateContent((HDF5Filter*)this->currentFileFilter(), fileName); break; case AbstractFileFilter::NETCDF: ui.cbFileType->setCurrentIndex(AbstractFileFilter::NETCDF); m_netcdfOptionsWidget->updateContent((NetCDFFilter*)this->currentFileFilter(), fileName); break; case AbstractFileFilter::FITS: #ifdef HAVE_FITS ui.cbFileType->setCurrentIndex(AbstractFileFilter::FITS); m_fitsOptionsWidget->updateContent((FITSFilter*)this->currentFileFilter(), fileName); #endif break; case AbstractFileFilter::Json: ui.cbFileType->setCurrentIndex(AbstractFileFilter::Json); m_jsonOptionsWidget->loadDocument(fileName); break; case AbstractFileFilter::ROOT: ui.cbFileType->setCurrentIndex(AbstractFileFilter::ROOT); m_rootOptionsWidget->updateContent((ROOTFilter*)this->currentFileFilter(), fileName); break; case AbstractFileFilter::NgspiceRawAscii: ui.cbFileType->setCurrentIndex(AbstractFileFilter::NgspiceRawAscii); break; case AbstractFileFilter::NgspiceRawBinary: ui.cbFileType->setCurrentIndex(AbstractFileFilter::NgspiceRawBinary); break; } } refreshPreview(); emit fileNameChanged(); } /*! saves the current filter settings */ void ImportFileWidget::saveFilter() { bool ok; QString text = QInputDialog::getText(this, i18n("Save Filter Settings as"), i18n("Filter name:"), QLineEdit::Normal, i18n("new filter"), &ok); if (ok && !text.isEmpty()) { //TODO //AsciiFilter::saveFilter() } } /*! opens a dialog for managing all available predefined filters. */ void ImportFileWidget::manageFilters() { //TODO } /*! Depending on the selected file type, activates the corresponding options in the data portion tab and populates the combobox with the available pre-defined fllter settings for the selected type. */ void ImportFileWidget::fileTypeChanged(int fileType) { ui.swOptions->setCurrentIndex(fileType); //default ui.lFilter->show(); ui.cbFilter->show(); //different file types show different number of tabs in ui.tabWidget. //when switching from the previous file type we re-set the tab widget to its original state //and remove/add the required tabs further below for (int i = 0; icount(); ++i) ui.tabWidget->count(); ui.tabWidget->addTab(ui.tabDataFormat, i18n("Data format")); ui.tabWidget->addTab(ui.tabDataPreview, i18n("Preview")); ui.tabWidget->addTab(ui.tabDataPortion, i18n("Data portion to read")); ui.lPreviewLines->show(); ui.sbPreviewLines->show(); ui.lStartColumn->show(); ui.sbStartColumn->show(); ui.lEndColumn->show(); ui.sbEndColumn->show(); showJsonModel(false); switch (fileType) { case AbstractFileFilter::Ascii: break; case AbstractFileFilter::Binary: ui.lStartColumn->hide(); ui.sbStartColumn->hide(); ui.lEndColumn->hide(); ui.sbEndColumn->hide(); break; case AbstractFileFilter::ROOT: ui.tabWidget->removeTab(1); // falls through case AbstractFileFilter::HDF5: case AbstractFileFilter::NETCDF: case AbstractFileFilter::FITS: ui.lFilter->hide(); ui.cbFilter->hide(); // hide global preview tab. we have our own ui.tabWidget->setTabText(0, i18n("Data format && preview")); ui.tabWidget->removeTab(1); ui.tabWidget->setCurrentIndex(0); break; case AbstractFileFilter::Image: ui.lPreviewLines->hide(); ui.sbPreviewLines->hide(); ui.lFilter->hide(); ui.cbFilter->hide(); break; case AbstractFileFilter::NgspiceRawAscii: case AbstractFileFilter::NgspiceRawBinary: ui.lStartColumn->hide(); ui.sbStartColumn->hide(); ui.lEndColumn->hide(); ui.sbEndColumn->hide(); ui.tabWidget->removeTab(0); ui.tabWidget->setCurrentIndex(0); break; case AbstractFileFilter::Json: ui.lFilter->hide(); ui.cbFilter->hide(); showJsonModel(true); break; default: DEBUG("unknown file type"); } m_hdf5OptionsWidget->clear(); m_netcdfOptionsWidget->clear(); m_rootOptionsWidget->clear(); int lastUsedFilterIndex = ui.cbFilter->currentIndex(); ui.cbFilter->clear(); ui.cbFilter->addItem( i18n("Automatic") ); ui.cbFilter->addItem( i18n("Custom") ); //TODO: populate the combobox with the available pre-defined filter settings for the selected type ui.cbFilter->setCurrentIndex(lastUsedFilterIndex); filterChanged(lastUsedFilterIndex); refreshPreview(); } const QStringList ImportFileWidget::selectedHDF5Names() const { return m_hdf5OptionsWidget->selectedHDF5Names(); } const QStringList ImportFileWidget::selectedNetCDFNames() const { return m_netcdfOptionsWidget->selectedNetCDFNames(); } const QStringList ImportFileWidget::selectedFITSExtensions() const { return m_fitsOptionsWidget->selectedFITSExtensions(); } const QStringList ImportFileWidget::selectedROOTNames() const { return m_rootOptionsWidget->selectedROOTNames(); } /*! shows the dialog with the information about the file(s) to be imported. */ void ImportFileWidget::fileInfoDialog() { QStringList files = ui.leFileName->text().split(';'); FileInfoDialog* dlg = new FileInfoDialog(this); dlg->setFiles(files); dlg->exec(); } /*! enables the options if the filter "custom" was chosen. Disables the options otherwise. */ void ImportFileWidget::filterChanged(int index) { // ignore filter for these formats if (ui.cbFileType->currentIndex() == AbstractFileFilter::HDF5 || ui.cbFileType->currentIndex() == AbstractFileFilter::NETCDF || ui.cbFileType->currentIndex() == AbstractFileFilter::Image || ui.cbFileType->currentIndex() == AbstractFileFilter::FITS || ui.cbFileType->currentIndex() == AbstractFileFilter::Json || ui.cbFileType->currentIndex() == AbstractFileFilter::ROOT) { ui.swOptions->setEnabled(true); return; } if (index == 0) { // "automatic" ui.swOptions->setEnabled(false); ui.bSaveFilter->setEnabled(false); } else if (index == 1) { //custom ui.swOptions->setEnabled(true); ui.bSaveFilter->setEnabled(true); } else { // predefined filter settings were selected. //load and show them in the GUI. //TODO } } void ImportFileWidget::refreshPreview() { if (m_suppressRefresh) return; WAIT_CURSOR; QString fileName = ui.leFileName->text(); #ifndef HAVE_WINDOWS if (!fileName.isEmpty() && fileName.at(0) != QDir::separator()) fileName = QDir::homePath() + QDir::separator() + fileName; #endif DEBUG("refreshPreview(): file name = " << fileName.toStdString()); QVector importedStrings; AbstractFileFilter::FileType fileType = (AbstractFileFilter::FileType)ui.cbFileType->currentIndex(); // generic table widget if (fileType == AbstractFileFilter::Ascii || fileType == AbstractFileFilter::Binary || fileType == AbstractFileFilter::Json || fileType == AbstractFileFilter::NgspiceRawAscii || fileType == AbstractFileFilter::NgspiceRawBinary) m_twPreview->show(); else m_twPreview->hide(); int lines = ui.sbPreviewLines->value(); bool ok = true; QTableWidget* tmpTableWidget{nullptr}; QStringList vectorNameList; QVector columnModes; DEBUG("Data File Type: " << ENUM_TO_STRING(AbstractFileFilter, FileType, fileType)); switch (fileType) { case AbstractFileFilter::Ascii: { ui.tePreview->clear(); AsciiFilter* filter = static_cast(this->currentFileFilter()); DEBUG("Data Source Type: " << ENUM_TO_STRING(LiveDataSource, SourceType, currentSourceType())); switch (currentSourceType()) { case LiveDataSource::SourceType::FileOrPipe: { importedStrings = filter->preview(fileName, lines); break; } case LiveDataSource::SourceType::LocalSocket: { QLocalSocket lsocket{this}; DEBUG("Local socket: CONNECT PREVIEW"); lsocket.connectToServer(fileName, QLocalSocket::ReadOnly); if (lsocket.waitForConnected()) { DEBUG("connected to local socket " << fileName.toStdString()); if (lsocket.waitForReadyRead()) importedStrings = filter->preview(lsocket); DEBUG("Local socket: DISCONNECT PREVIEW"); lsocket.disconnectFromServer(); // read-only socket is disconnected immediately (no waitForDisconnected()) } else { DEBUG("failed connect to local socket " << fileName.toStdString() << " - " << lsocket.errorString().toStdString()); } break; } case LiveDataSource::SourceType::NetworkTcpSocket: { QTcpSocket tcpSocket{this}; tcpSocket.connectToHost(host(), port().toInt(), QTcpSocket::ReadOnly); if (tcpSocket.waitForConnected()) { DEBUG("connected to TCP socket"); if ( tcpSocket.waitForReadyRead() ) importedStrings = filter->preview(tcpSocket); tcpSocket.disconnectFromHost(); } else { DEBUG("failed to connect to TCP socket " << " - " << tcpSocket.errorString().toStdString()); } break; } case LiveDataSource::SourceType::NetworkUdpSocket: { QUdpSocket udpSocket{this}; DEBUG("UDP Socket: CONNECT PREVIEW, state = " << udpSocket.state()); udpSocket.bind(QHostAddress(host()), port().toInt()); udpSocket.connectToHost(host(), 0, QUdpSocket::ReadOnly); if (udpSocket.waitForConnected()) { DEBUG(" connected to UDP socket " << host().toStdString() << ':' << port().toInt()); if (!udpSocket.waitForReadyRead(2000) ) DEBUG(" ERROR: not ready for read after 2 sec"); if (udpSocket.hasPendingDatagrams()) { DEBUG(" has pending data"); } else { DEBUG(" has no pending data"); } importedStrings = filter->preview(udpSocket); DEBUG("UDP Socket: DISCONNECT PREVIEW, state = " << udpSocket.state()); udpSocket.disconnectFromHost(); } else { DEBUG("failed to connect to UDP socket " << " - " << udpSocket.errorString().toStdString()); } break; } case LiveDataSource::SourceType::SerialPort: { QSerialPort sPort{this}; DEBUG(" Port: " << serialPort().toStdString() << ", Settings: " << baudRate() << ',' << sPort.dataBits() << ',' << sPort.parity() << ',' << sPort.stopBits()); sPort.setPortName(serialPort()); sPort.setBaudRate(baudRate()); if (sPort.open(QIODevice::ReadOnly)) { if (sPort.waitForReadyRead(2000)) importedStrings = filter->preview(sPort); else { DEBUG(" ERROR: not ready for read after 2 sec"); } sPort.close(); } else { DEBUG(" ERROR: failed to open serial port. error: " << sPort.error()); } break; } case LiveDataSource::SourceType::MQTT: { #ifdef HAVE_MQTT qDebug()<<"preview mqtt, is it ready:"<vectorNames().clear(); QMapIterator i(m_lastMessage); while(i.hasNext()) { i.next(); qDebug()<<"calling ascii mqtt preview"<< importedStrings << " "<mqttPreview(importedStrings, QString(i.value().payload().data()), i.key().name() ); if(importedStrings.isEmpty()) break; } QMapIterator j(m_messageArrived); while(j.hasNext()) { j.next(); qDebug()<<"Set false after preview: "<vectorNames(); columnModes = filter->columnModes(); break; } case AbstractFileFilter::Binary: { ui.tePreview->clear(); BinaryFilter *filter = (BinaryFilter *)this->currentFileFilter(); importedStrings = filter->preview(fileName, lines); tmpTableWidget = m_twPreview; break; } case AbstractFileFilter::Image: { ui.tePreview->clear(); QImage image(fileName); QTextCursor cursor = ui.tePreview->textCursor(); cursor.insertImage(image); RESET_CURSOR; return; } case AbstractFileFilter::HDF5: { HDF5Filter *filter = (HDF5Filter *)this->currentFileFilter(); lines = m_hdf5OptionsWidget->lines(); importedStrings = filter->readCurrentDataSet(fileName, NULL, ok, AbstractFileFilter::Replace, lines); tmpTableWidget = m_hdf5OptionsWidget->previewWidget(); break; } case AbstractFileFilter::NETCDF: { NetCDFFilter *filter = (NetCDFFilter *)this->currentFileFilter(); lines = m_netcdfOptionsWidget->lines(); importedStrings = filter->readCurrentVar(fileName, NULL, AbstractFileFilter::Replace, lines); tmpTableWidget = m_netcdfOptionsWidget->previewWidget(); break; } case AbstractFileFilter::FITS: { FITSFilter* filter = (FITSFilter*)this->currentFileFilter(); lines = m_fitsOptionsWidget->lines(); // update file name (may be any file type) m_fitsOptionsWidget->updateContent(filter, fileName); QString extensionName = m_fitsOptionsWidget->extensionName(&ok); if (!extensionName.isEmpty()) { DEBUG(" extension name = " << extensionName.toStdString()); fileName = extensionName; } DEBUG(" file name = " << fileName.toStdString()); bool readFitsTableToMatrix; importedStrings = filter->readChdu(fileName, &readFitsTableToMatrix, lines); emit checkedFitsTableToMatrix(readFitsTableToMatrix); tmpTableWidget = m_fitsOptionsWidget->previewWidget(); break; } case AbstractFileFilter::Json: { ui.tePreview->clear(); m_jsonOptionsWidget->loadDocument(fileName); JsonFilter *filter = (JsonFilter*)this->currentFileFilter(); m_jsonOptionsWidget->applyFilterSettings(filter, ui.tvJson->currentIndex()); importedStrings = filter->preview(fileName); tmpTableWidget = m_twPreview; columnModes = filter->columnModes(); break; } case AbstractFileFilter::ROOT: { ROOTFilter *filter = (ROOTFilter *)this->currentFileFilter(); lines = m_rootOptionsWidget->lines(); m_rootOptionsWidget->setNBins(filter->binsInCurrentHistogram(fileName)); importedStrings = filter->previewCurrentHistogram( fileName, m_rootOptionsWidget->startBin(), qMin(m_rootOptionsWidget->startBin() + m_rootOptionsWidget->lines() - 1, m_rootOptionsWidget->endBin()) ); tmpTableWidget = m_rootOptionsWidget->previewWidget(); // the last vector element contains the column names vectorNameList = importedStrings.last(); importedStrings.removeLast(); columnModes = QVector(vectorNameList.size(), AbstractColumn::Numeric); break; } case AbstractFileFilter::NgspiceRawAscii: { ui.tePreview->clear(); NgspiceRawAsciiFilter* filter = (NgspiceRawAsciiFilter*)this->currentFileFilter(); importedStrings = filter->preview(fileName, lines); tmpTableWidget = m_twPreview; vectorNameList = filter->vectorNames(); columnModes = filter->columnModes(); break; } case AbstractFileFilter::NgspiceRawBinary: { ui.tePreview->clear(); NgspiceRawBinaryFilter* filter = (NgspiceRawBinaryFilter*)this->currentFileFilter(); importedStrings = filter->preview(fileName, lines); tmpTableWidget = m_twPreview; vectorNameList = filter->vectorNames(); columnModes = filter->columnModes(); break; } } // fill the table widget tmpTableWidget->setRowCount(0); tmpTableWidget->setColumnCount(0); if( !importedStrings.isEmpty() ) { //QDEBUG("importedStrings =" << importedStrings); if (!ok) { // show imported strings as error message tmpTableWidget->setRowCount(1); tmpTableWidget->setColumnCount(1); QTableWidgetItem* item = new QTableWidgetItem(); item->setText(importedStrings[0][0]); tmpTableWidget->setItem(0, 0, item); } else { //TODO: maxrows not used const int rows = qMax(importedStrings.size(), 1); const int maxColumns = 300; tmpTableWidget->setRowCount(rows); for (int i = 0; i < rows; ++i) { // QDEBUG("imported string " << importedStrings[i]); int cols = importedStrings[i].size() > maxColumns ? maxColumns : importedStrings[i].size(); // new if (cols > tmpTableWidget->columnCount()) tmpTableWidget->setColumnCount(cols); for (int j = 0; j < cols; ++j) { QTableWidgetItem* item = new QTableWidgetItem(importedStrings[i][j]); tmpTableWidget->setItem(i, j, item); } } // set header if columnMode available for (int i = 0; i < qMin(tmpTableWidget->columnCount(), columnModes.size()); ++i) { QString columnName = QString::number(i+1); if (i < vectorNameList.size()) columnName = vectorNameList[i]; auto* item = new QTableWidgetItem(columnName + QLatin1String(" {") + ENUM_TO_STRING(AbstractColumn, ColumnMode, columnModes[i]) + QLatin1String("}")); item->setTextAlignment(Qt::AlignLeft); item->setIcon(AbstractColumn::iconForMode(columnModes[i])); tmpTableWidget->setHorizontalHeaderItem(i, item); } } tmpTableWidget->horizontalHeader()->resizeSections(QHeaderView::ResizeToContents); m_fileEmpty = false; } else { m_fileEmpty = true; } emit previewRefreshed(); RESET_CURSOR; } void ImportFileWidget::updateTypeChanged(int idx) { const LiveDataSource::UpdateType UpdateType = static_cast(idx); switch (UpdateType) { case LiveDataSource::UpdateType::TimeInterval: ui.lUpdateInterval->show(); ui.sbUpdateInterval->show(); break; case LiveDataSource::UpdateType::NewData: ui.lUpdateInterval->hide(); ui.sbUpdateInterval->hide(); } } void ImportFileWidget::readingTypeChanged(int idx) { const LiveDataSource::ReadingType readingType = static_cast(idx); const LiveDataSource::SourceType sourceType = static_cast(ui.cbSourceType->currentIndex()); if (sourceType == LiveDataSource::SourceType::NetworkTcpSocket || sourceType == LiveDataSource::SourceType::LocalSocket || sourceType == LiveDataSource::SourceType::SerialPort || readingType == LiveDataSource::ReadingType::TillEnd || readingType == LiveDataSource::ReadingType::WholeFile) { ui.lSampleSize->hide(); ui.sbSampleSize->hide(); } else { ui.lSampleSize->show(); ui.sbSampleSize->show(); } if (readingType == LiveDataSource::ReadingType::WholeFile) { ui.lKeepLastValues->hide(); ui.sbKeepNValues->hide(); } else { ui.lKeepLastValues->show(); ui.sbKeepNValues->show(); } } void ImportFileWidget::sourceTypeChanged(int idx) { const LiveDataSource::SourceType sourceType = static_cast(idx); // enable/disable "on new data"-option const QStandardItemModel* model = qobject_cast(ui.cbUpdateType->model()); QStandardItem* item = model->item(LiveDataSource::UpdateType::NewData); switch (sourceType) { case LiveDataSource::SourceType::FileOrPipe: ui.lFileName->show(); ui.leFileName->show(); ui.bFileInfo->show(); ui.bOpen->show(); ui.chbLinkFile->show(); ui.lSampleSize->show(); ui.sbSampleSize->show(); ui.cbBaudRate->hide(); ui.lBaudRate->hide(); ui.lHost->hide(); ui.leHost->hide(); ui.lPort->hide(); ui.lePort->hide(); ui.cbSerialPort->hide(); ui.lSerialPort->hide(); item->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled); fileNameChanged(ui.leFileName->text()); break; case LiveDataSource::SourceType::NetworkTcpSocket: case LiveDataSource::SourceType::NetworkUdpSocket: ui.lHost->show(); ui.leHost->show(); ui.lePort->show(); ui.lPort->show(); if (sourceType == LiveDataSource::SourceType::NetworkTcpSocket) { ui.lSampleSize->hide(); ui.sbSampleSize->hide(); } else { ui.lSampleSize->show(); ui.sbSampleSize->show(); } ui.lBaudRate->hide(); ui.cbBaudRate->hide(); ui.lSerialPort->hide(); ui.cbSerialPort->hide(); ui.lFileName->hide(); ui.leFileName->hide(); ui.bFileInfo->hide(); ui.bOpen->hide(); ui.chbLinkFile->hide(); item->setFlags(item->flags() & ~(Qt::ItemIsSelectable | Qt::ItemIsEnabled)); ui.gbOptions->setEnabled(true); ui.bManageFilters->setEnabled(true); ui.cbFilter->setEnabled(true); ui.cbFileType->setEnabled(true); break; case LiveDataSource::SourceType::LocalSocket: ui.lFileName->show(); ui.leFileName->show(); ui.bOpen->show(); ui.lSampleSize->hide(); ui.sbSampleSize->hide(); ui.bFileInfo->hide(); ui.cbBaudRate->hide(); ui.lBaudRate->hide(); ui.lHost->hide(); ui.leHost->hide(); ui.lPort->hide(); ui.lePort->hide(); ui.cbSerialPort->hide(); ui.lSerialPort->hide(); ui.chbLinkFile->hide(); item->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled); ui.gbOptions->setEnabled(true); ui.bManageFilters->setEnabled(true); ui.cbFilter->setEnabled(true); ui.cbFileType->setEnabled(true); break; case LiveDataSource::SourceType::SerialPort: ui.lBaudRate->show(); ui.cbBaudRate->show(); ui.lSerialPort->show(); ui.cbSerialPort->show(); ui.lSampleSize->show(); ui.sbSampleSize->show(); ui.lHost->hide(); ui.leHost->hide(); ui.lePort->hide(); ui.lPort->hide(); ui.lFileName->hide(); ui.leFileName->hide(); ui.bFileInfo->hide(); ui.bOpen->hide(); ui.chbLinkFile->hide(); item->setFlags(item->flags() & ~(Qt::ItemIsSelectable | Qt::ItemIsEnabled)); ui.cbFileType->setEnabled(true); ui.gbOptions->setEnabled(true); ui.bManageFilters->setEnabled(true); ui.cbFilter->setEnabled(true); break; case LiveDataSource::SourceType::MQTT: #ifdef HAVE_MQTT + item->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled); + ui.lBaudRate->hide(); ui.cbBaudRate->hide(); ui.lSerialPort->hide(); ui.cbSerialPort->hide(); ui.lHost->show(); ui.leHost->show(); ui.lePort->show(); ui.lPort->show(); ui.lFileName->hide(); ui.leFileName->hide(); ui.bFileInfo->hide(); ui.bOpen->hide(); ui.chbLinkFile->hide(); ui.cbFileType->setEnabled(true); ui.leID->hide(); ui.lMqttID->hide(); ui.lePassword->hide(); ui.lPassword->hide(); ui.leUsername->hide(); ui.lUsername->hide(); ui.cbQos->show(); ui.lQos->show(); ui.twTopics->show(); ui.lTopicSearch->show(); ui.leTopics->show(); ui.twSubscriptions->show(); ui.chbAuthentication->show(); ui.chbID->show(); ui.bSubscribe->show(); ui.bUnsubscribe->show(); ui.bConnect->show(); ui.gbOptions->setEnabled(true); ui.bManageFilters->setEnabled(true); ui.cbFilter->setEnabled(true); ui.gbMqttWill->show(); ui.chbWill->show(); ui.chbWillRetain->hide(); ui.cbWillQoS->hide(); ui.cbWillMessageType->hide(); ui.cbWillTopic->hide(); ui.cbWillUpdate->hide(); ui.leWillOwnMessage->hide(); ui.leWillUpdateInterval->setValidator(new QIntValidator(2, 1000000) ); ui.leWillUpdateInterval->hide(); ui.lWillMessageType->hide(); ui.lWillOwnMessage->hide(); ui.lWillQos->hide(); ui.lWillTopic->hide(); ui.lWillUpdate->hide(); ui.lWillUpdateInterval->hide(); ui.lwWillStatistics->hide(); ui.lWillStatistics->hide(); ui.gbManageSubscriptions->setEnabled(false); checkConnectEnable(); if(ui.chbWill->isChecked()) { ui.chbWillRetain->show(); ui.cbWillQoS->show(); ui.cbWillMessageType->show(); ui.cbWillTopic->show(); ui.cbWillUpdate->show(); ui.lWillMessageType->show(); ui.lWillQos->show(); ui.lWillTopic->show(); ui.lWillUpdate->show(); if (ui.cbWillMessageType->currentIndex() == static_cast(MQTTClient::WillMessageType::OwnMessage) ) { ui.leWillOwnMessage->show(); ui.lWillOwnMessage->show(); } else if(ui.cbWillMessageType->currentIndex() == static_cast(MQTTClient::WillMessageType::Statistics) ){ qDebug()<<"source type changed show statistics"; ui.lWillStatistics->show(); ui.lwWillStatistics->show(); } if(ui.cbWillUpdate->currentIndex() == 0) { ui.leWillUpdateInterval->show(); ui.lWillUpdateInterval->show(); } else if (ui.cbWillUpdate->currentIndex() == 1) { ui.leWillUpdateInterval->hide(); ui.lWillUpdateInterval->hide(); } } #endif break; } // "whole file" item model = qobject_cast(ui.cbReadingType->model()); item = model->item(LiveDataSource::ReadingType::WholeFile); if (sourceType == LiveDataSource::SourceType::FileOrPipe) item->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled); else item->setFlags(item->flags() & ~(Qt::ItemIsSelectable | Qt::ItemIsEnabled)); //"update options" groupbox can be deactived for "file and pipe" if the file is invalid. //Activate the groupbox when switching from "file and pipe" to a different source type. if (sourceType != LiveDataSource::SourceType::FileOrPipe) ui.gbUpdateOptions->setEnabled(true); emit sourceTypeChanged(); refreshPreview(); } void ImportFileWidget::initializeAndFillPortsAndBaudRates() { for (int i = 2; i < ui.swOptions->count(); ++i) ui.swOptions->removeWidget(ui.swOptions->widget(i)); const int size = ui.cbFileType->count(); for (int i = 2; i < size; ++i) ui.cbFileType->removeItem(2); ui.cbBaudRate->hide(); ui.lBaudRate->hide(); ui.lHost->hide(); ui.leHost->hide(); ui.lPort->hide(); ui.lePort->hide(); ui.cbSerialPort->hide(); ui.lSerialPort->hide(); ui.cbBaudRate->addItems(LiveDataSource::supportedBaudRates()); ui.cbSerialPort->addItems(LiveDataSource::availablePorts()); ui.tabWidget->removeTab(2); } #ifdef HAVE_MQTT /*! *\brief called when ID checkbox's state is changed, if checked a lineEdit is shown so the user can set the ID * \param state the state of the checbox */ void ImportFileWidget::idChecked(int state) { if (state == 2) { ui.leID->show(); ui.lMqttID->show(); } else if (state == 0) { ui.leID->hide(); ui.lMqttID->hide(); } } /*! *\brief called when authentication checkbox's state is changed, * if checked two lineEdits are shown so the user can set the username and password * * \param state the state of the checbox */ void ImportFileWidget::authenticationChecked(int state) { if(state == 2) { ui.leUsername->show(); ui.lePassword->show(); ui.lPassword->show(); ui.lUsername->show(); } else if (state == 0) { ui.leUsername->hide(); ui.lePassword->hide(); ui.lUsername->hide(); ui.lPassword->hide(); } } /*! *\brief called when the connect/disconnect button is pressed * makes the connection to the given MQTT broker, with the given options * or disconnects from the broker */ void ImportFileWidget::mqttConnection() { if(m_client->state() == QMqttClient::ClientState::Disconnected) { //Check whether the set options are valid and a connection can be made, or not const bool hostSet = !ui.leHost->text().isEmpty(); const bool portSet = !ui.lePort->text().isEmpty(); const bool idUsed = ui.chbID->isChecked(); const bool idSet = !ui.leID->text().isEmpty(); const bool idValid = !(idUsed && !idSet); const bool authenticationUsed = ui.chbAuthentication->isChecked(); const bool usernameSet = !ui.leUsername->text().isEmpty(); const bool passwordSet = !ui.lePassword->text().isEmpty(); const bool authenticationValid = ! (authenticationUsed && ( !usernameSet || !passwordSet) ); const bool valid =hostSet && portSet && idValid && authenticationValid; if (valid) { m_client->setHostname(ui.leHost->text().simplified()); m_client->setPort(ui.lePort->text().toUInt()); if(ui.chbID->isChecked()) m_client->setClientId(ui.leID->text().simplified()); if(ui.chbAuthentication->isChecked()) { m_client->setUsername(ui.leUsername->text().simplified()); m_client->setPassword(ui.lePassword->text().simplified()); } qDebug()<< ui.leHost->text() << " " << m_client->hostname() << " " << m_client->port(); qDebug()<<"Trying to connect"; m_client->connectToHost(); m_connectTimeoutTimer->start(); } } else if (m_client->state() == QMqttClient::ClientState::Connected) { qDebug()<<"Disconnecting from mqtt broker" ; m_client->disconnectFromHost(); } } /*! *\brief called when the client connects to the broker succesfully, it subscribes to every topic (# wildcard) * in order to later list every available topic */ void ImportFileWidget::onMqttConnect() { if(m_client->error() == QMqttClient::NoError) { m_connectTimeoutTimer->stop(); ui.gbManageSubscriptions->setEnabled(true); ui.bConnect->setText("Disconnect"); ui.leHost->setEnabled(false); ui.lePort->setEnabled(false); ui.lePassword->setEnabled(false); ui.leUsername->setEnabled(false); ui.leID->setEnabled(false); ui.cbSourceType->setEnabled(false); ui.chbAuthentication->setEnabled(false); ui.chbID->setEnabled(false); ui.chbRetain->setEnabled(false); QMessageBox::information(this, "Connection successful", "Connection established"); //subscribing to every topic (# wildcard) in order to later list every available topic QMqttTopicFilter globalFilter{"#"}; m_mainSubscription = m_client->subscribe(globalFilter, 1); if(!m_mainSubscription) QMessageBox::information(this, "Couldn't subscribe", "Something went wrong"); } } /*! *\brief called when the client disconnects from the broker succesfully * removes every information about the former connection */ void ImportFileWidget::onMqttDisconnect() { ui.gbManageSubscriptions->setEnabled(false); ui.bConnect->setText("Connect"); ui.leHost->setEnabled(true); ui.leHost->clear(); ui.lePort->setEnabled(true); ui.lePort->clear(); ui.lePassword->setEnabled(true); ui.lePassword->clear(); ui.leUsername->setEnabled(true); ui.leUsername->clear(); ui.leID->setEnabled(true); ui.leID->clear(); ui.twSubscriptions->clear(); ui.twTopics->clear(); ui.chbRetain->setEnabled(true); ui.cbSourceType->setEnabled(true); ui.chbAuthentication->setEnabled(true); ui.chbID->setEnabled(true); m_mqttReadyForPreview = false; m_mqttSubscriptions.clear(); m_topicCompleter = new QCompleter; m_subscriptionCompleter = new QCompleter; m_topicList.clear(); m_searching = false; m_searchTimer->stop(); m_messageArrived.clear(); m_lastMessage.clear(); } /*! *\brief called when the subscribe button is pressed * subscribes to the topic represented by the current item of twTopics */ void ImportFileWidget::mqttSubscribe() { QString name; QTreeWidgetItem *item = ui.twTopics->currentItem(); if(item != nullptr) { QTreeWidgetItem *tempItem = item; //determine the topic name that the current item represents name.prepend(item->text(0)); if(item->childCount() != 0) name.append("/#"); while(tempItem->parent() != nullptr) { tempItem = tempItem->parent(); name.prepend(tempItem->text(0) + "/"); } QList topLevelList = ui.twSubscriptions->findItems(name, Qt::MatchExactly); //check if the subscription already exists if(topLevelList.isEmpty() || topLevelList.first()->parent() != nullptr) { qDebug() << name; bool foundSuperior = false; for(int i = 0; i < ui.twSubscriptions->topLevelItemCount(); ++i) { qDebug()<topLevelItemCount(); //if the new subscirptions contains an already existing one, we remove the inferior one if(checkTopicContains(name, ui.twSubscriptions->topLevelItem(i)->text(0)) && name != ui.twSubscriptions->topLevelItem(i)->text(0)) { qDebug()<<"1"<topLevelItem(i)->text(0); unsubscribeFromTopic(ui.twSubscriptions->topLevelItem(i)->text(0)); i--; continue; } qDebug()<<"checked inferior"; //if there is a subscription containing the new one we set foundSuperior true if(checkTopicContains(ui.twSubscriptions->topLevelItem(i)->text(0), name) && name != ui.twSubscriptions->topLevelItem(i)->text(0)) { foundSuperior = true; qDebug()<<"2"<topLevelItem(i)->text(0); break; } qDebug()<<"checked superior"; } //if there wasn't a superior subscription we can subscribe to the new topic if(!foundSuperior) { qDebug()<<"Adding new topic"; QStringList toplevelName; toplevelName.push_back(name); QTreeWidgetItem* newTopLevelItem = new QTreeWidgetItem(toplevelName); ui.twSubscriptions->addTopLevelItem(newTopLevelItem); QMqttTopicFilter filter {name}; QMqttSubscription *temp_subscription = m_client->subscribe(filter, static_cast (ui.cbQos->currentText().toUInt()) ); if(temp_subscription) { m_mqttSubscriptions.push_back(temp_subscription); connect(temp_subscription, &QMqttSubscription::messageReceived, this, &ImportFileWidget::mqttSubscriptionMessageReceived); emit subscriptionsChanged(); } if(name.endsWith("#")) { //adding every topic that the subscription contains to twSubscriptions addSubscriptionChildren(item, newTopLevelItem); //if an already existing subscription contains a topic that the new subscription also contains //we decompose the already existing subscription //by unsubscribing from its topics, that are present in the new subscription as well QStringList nameList = name.split('/', QString::SkipEmptyParts); QString root = nameList.first(); QVector children; for(int i = 0; i < ui.twSubscriptions->topLevelItemCount(); ++i) { if(ui.twSubscriptions->topLevelItem(i)->text(0).startsWith(root) && name != ui.twSubscriptions->topLevelItem(i)->text(0)) { children.clear(); //get the "leaf" children of the inspected subscription findSubscriptionLeafChildren(children, ui.twSubscriptions->topLevelItem(i)); for(int j = 0; j < children.size(); ++j) { if(checkTopicContains(name, children[j]->text(0))) { //if the new subscription contains a topic, we unsubscribe from it ui.twSubscriptions->setCurrentItem(children[j]); mqttUnsubscribe(); } } } } } manageCommonLevelSubscriptions(); updateWillTopics(); updateSubscriptionCompleter(); } else { QMessageBox::warning(this, "Warning", "You already subscribed to a topic containing this one"); } } else QMessageBox::warning(this, "Warning", "You already subscribed to this topic"); } else QMessageBox::warning(this, "Warning", "You didn't select any item from the Tree Widget"); } /*! *\brief called when the unsubscribe button is pressed * unsubscribes from the topic represented by the current item of twSubscription */ void ImportFileWidget::mqttUnsubscribe() { QTreeWidgetItem* unsubscribeItem = ui.twSubscriptions->currentItem(); if(unsubscribeItem != nullptr) { //if it is a top level item, meaning a topic that we really subscribed to(not one that belongs to a subscription) //we can simply unsubscribe from it if(unsubscribeItem->parent() == nullptr) unsubscribeFromTopic(unsubscribeItem->text(0)); //otherwise we remove the selected item, but subscribe to every other topic, that was contained by //the selected item's parent subscription(top level item of twSubscripitons) else{ while(unsubscribeItem->parent() != nullptr) { for(int i = 0; i < unsubscribeItem->parent()->childCount(); ++i) { if(unsubscribeItem->text(0) != unsubscribeItem->parent()->child(i)->text(0)) { QMqttTopicFilter filter {unsubscribeItem->parent()->child(i)->text(0)}; QMqttSubscription *temp_subscription = m_client->subscribe(filter, static_cast (ui.cbQos->currentText().toUInt()) ); ui.twSubscriptions->addTopLevelItem(unsubscribeItem->parent()->takeChild(i)); if(temp_subscription) { m_mqttSubscriptions.push_back(temp_subscription); connect(temp_subscription, &QMqttSubscription::messageReceived, this, &ImportFileWidget::mqttSubscriptionMessageReceived); emit subscriptionsChanged(); } i--; } } unsubscribeItem = unsubscribeItem->parent(); } unsubscribeFromTopic(unsubscribeItem->text(0)); //check if any common topics were subscribed, if possible merge them manageCommonLevelSubscriptions(); } updateWillTopics(); updateSubscriptionCompleter(); } else QMessageBox::warning(this, "Warning", "You didn't select any item from the Tree Widget"); } /*! *\brief called when the client receives a message * if the message arrived from a new topic, the topic is put in twTopics */ void ImportFileWidget::mqttMessageReceived(const QByteArray &message , const QMqttTopicName &topic) { if(!m_addedTopics.contains(topic.name())) { m_addedTopics.push_back(topic.name()); QStringList name; QChar sep = '/'; QString rootName; if(topic.name().contains(sep)) { QStringList list = topic.name().split(sep, QString::SkipEmptyParts); if(!list.isEmpty()) { rootName = list.at(0); name.append(list.at(0)); QTreeWidgetItem* currentItem; int topItemIdx = -1; //check whether the first level of the topic can be found in twTopics for(int i = 0; i < ui.twTopics->topLevelItemCount(); ++i) { if(ui.twTopics->topLevelItem(i)->text(0) == list.at(0)) { topItemIdx = i; break; } } //if not we simply add every level of the topic to the tree if( topItemIdx < 0) { currentItem = new QTreeWidgetItem(name); ui.twTopics->addTopLevelItem(currentItem); for(int i = 1; i < list.size(); ++i) { name.clear(); name.append(list.at(i)); currentItem->addChild(new QTreeWidgetItem(name)); currentItem = currentItem->child(0); } } //otherwise we search for the first level that isn't part of the tree, //then add every level of the topic to the tree from that certain level else { currentItem = ui.twTopics->topLevelItem(topItemIdx); int listIdx = 1; for(; listIdx < list.size(); ++listIdx) { QTreeWidgetItem* childItem = nullptr; bool found = false; for(int j = 0; j < currentItem->childCount(); ++j) { childItem = currentItem->child(j); if(childItem->text(0) == list.at(listIdx)) { found = true; currentItem = childItem; break; } } if(!found) { //this is the level that isn't present in the tree break; } } //add every level to the tree starting with the first level that isn't part of the tree for(; listIdx < list.size(); ++listIdx) { name.clear(); name.append(list.at(listIdx)); currentItem->addChild(new QTreeWidgetItem(name)); currentItem = currentItem->child(currentItem->childCount() - 1); } } } } else { rootName = topic.name(); name.append(topic.name()); ui.twTopics->addTopLevelItem(new QTreeWidgetItem(name)); } //if a subscribed topic contains the new topic, we have to update twSubscriptions for(int i = 0; i < ui.twSubscriptions->topLevelItemCount(); ++i) { QStringList subscriptionName = ui.twSubscriptions->topLevelItem(i)->text(0).split('/', QString::SkipEmptyParts); if(!subscriptionName.isEmpty()) { if (rootName == subscriptionName.first()) { updateSubscriptionTree(); break; } } } //signals that a newTopic was added, in order to fill the completer of leTopics qDebug()<<"emit signal"; emit newTopic(rootName); } } /*! *\brief called when a new topic is added to the tree(twTopics) * appends the topic's root to the topicList if it isn't in the list already * then sets the completer for leTopics */ void ImportFileWidget::setTopicCompleter(const QString& topic) { if(!m_topicList.contains(topic)) { m_topicList.append(topic); if(!m_searching) { m_topicCompleter = new QCompleter(m_topicList, this); m_topicCompleter->setCompletionMode(QCompleter::PopupCompletion); m_topicCompleter->setCaseSensitivity(Qt::CaseSensitive); ui.leTopics->setCompleter(m_topicCompleter); } } } /*! *\brief called when 10 seconds passed since the last time the user searched for a certain root in twTopics * enables updating the completer for le */ void ImportFileWidget::topicTimeout() { qDebug()<<"lejart ido"; m_searching = false; m_searchTimer->stop(); } /*! *\brief called when the client receives a message from a subscribed topic (that isn't the "#" wildcard) */ void ImportFileWidget::mqttSubscriptionMessageReceived(const QMqttMessage &msg) { qDebug()<<"message received from: "< i(m_messageArrived); while(i.hasNext()) { i.next(); if(i.value() == false ) { qDebug()<<"Found false: "<show(); ui.cbWillQoS->show(); ui.cbWillMessageType->show(); ui.cbWillTopic->show(); ui.cbWillUpdate->show(); ui.lWillMessageType->show(); ui.lWillQos->show(); ui.lWillTopic->show(); ui.lWillUpdate->show(); if (ui.cbWillMessageType->currentIndex() == static_cast(MQTTClient::WillMessageType::OwnMessage) ) { ui.leWillOwnMessage->show(); ui.lWillOwnMessage->show(); } else if(ui.cbWillMessageType->currentIndex() == static_cast(MQTTClient::WillMessageType::Statistics) ) { ui.lWillStatistics->show(); ui.lwWillStatistics->show(); } if(ui.cbWillUpdate->currentIndex() == 0) { ui.leWillUpdateInterval->show(); ui.lWillUpdateInterval->show(); } } else if (state == Qt::Unchecked) { qDebug()<<"will use unchecked"; ui.chbWillRetain->hide(); ui.cbWillQoS->hide(); ui.cbWillMessageType->hide(); ui.cbWillTopic->hide(); ui.cbWillUpdate->hide(); ui.leWillOwnMessage->hide(); ui.leWillUpdateInterval->hide(); ui.lWillMessageType->hide(); ui.lWillOwnMessage->hide(); ui.lWillQos->hide(); ui.lWillTopic->hide(); ui.lWillUpdate->hide(); ui.lWillUpdateInterval->hide(); ui.lWillStatistics->hide(); ui.lwWillStatistics->hide(); } } /*! *\brief called when the selected will message type is changed, * shows the options for the selected message type, hides the irrelevant ones * * \param type the selected will message type */ void ImportFileWidget::willMessageTypeChanged(int type) { if(static_cast (type) == MQTTClient::WillMessageType::OwnMessage) { ui.leWillOwnMessage->show(); ui.lWillOwnMessage->show(); ui.lWillStatistics->hide(); ui.lwWillStatistics->hide(); } else if(static_cast (type) == MQTTClient::WillMessageType::LastMessage) { ui.leWillOwnMessage->hide(); ui.lWillOwnMessage->hide(); ui.lWillStatistics->hide(); ui.lwWillStatistics->hide(); } else if(static_cast (type) == MQTTClient::WillMessageType::Statistics) { qDebug()<<"will message type changed show statistics"; ui.lWillStatistics->show(); ui.lwWillStatistics->show(); ui.leWillOwnMessage->hide(); ui.lWillOwnMessage->hide(); } } /*! *\brief called when newTopicForWill signal is emitted, * updates the topics that can be selected as the will message's topic */ void ImportFileWidget::updateWillTopics() { QString current = ui.cbWillTopic->currentText(); ui.cbWillTopic->clear(); //Get every leaf subscribed topic QVector children; for(int i = 0; i < ui.twSubscriptions->topLevelItemCount(); ++i) { findSubscriptionLeafChildren(children, ui.twSubscriptions->topLevelItem(i)); } for(int i = 0; i < children.size(); ++i) { ui.cbWillTopic->addItem(children[i]->text(0)); } //Set back the previous value if(!current.isEmpty()) ui.cbWillTopic->setCurrentText(current); } /*! *\brief called when the selected update type for the will message is changed, * shows the options for the selected update type, hides the irrelevant ones * * \param type the selected will update type */ void ImportFileWidget::willUpdateTypeChanged(int updateType) { if(static_cast(updateType) == MQTTClient::WillUpdateType::TimePeriod) { ui.leWillUpdateInterval->show(); ui.lWillUpdateInterval->show(); } else if (static_cast(updateType) == MQTTClient::WillUpdateType::OnClick) { ui.leWillUpdateInterval->hide(); ui.lWillUpdateInterval->hide(); } } /*! *\brief called when the clientError of the MQTT client changes * * \param clientError the current error of the client */ void ImportFileWidget::mqttErrorChanged(QMqttClient::ClientError clientError) { switch (clientError) { case QMqttClient::BadUsernameOrPassword: QMessageBox::warning(this, "Couldn't connect", "Bad username or password"); break; case QMqttClient::IdRejected: QMessageBox::warning(this, "Couldn't connect", "The client ID wasn't accepted"); break; case QMqttClient::ServerUnavailable: QMessageBox::warning(this, "Server unavailable", "The network connection has been established, but the service is unavailable on the broker side."); break; case QMqttClient::NotAuthorized: QMessageBox::warning(this, "Couldn't connect", "The client is not authorized to connect."); break; case QMqttClient::UnknownError: QMessageBox::warning(this, "Unknown MQTT error", "An unknown error occurred."); break; default: break; } } /*! *\brief called when leTopics' text is changed * if the rootName can be found in twTopics, then we scroll it to the top of the tree widget * * \param rootName the current text of leTopics */ void ImportFileWidget::scrollToTopicTreeItem(const QString& rootName) { m_searching = true; m_searchTimer->start(); int topItemIdx = -1; for(int i = 0; i < ui.twTopics->topLevelItemCount(); ++i) if(ui.twTopics->topLevelItem(i)->text(0) == rootName) { topItemIdx = i; break; } if(topItemIdx >= 0) { ui.twTopics->scrollToItem(ui.twTopics->topLevelItem(topItemIdx), QAbstractItemView::ScrollHint::PositionAtTop); } } /*! *\brief called when leSubscriptions' text is changed * if the rootName can be found in twSubscriptions, then we scroll it to the top of the tree widget * * \param rootName the current text of leSubscriptions */ void ImportFileWidget::scrollToSubsriptionTreeItem(const QString& rootName) { m_searching = true; m_searchTimer->start(); int topItemIdx = -1; for(int i = 0; i < ui.twSubscriptions->topLevelItemCount(); ++i) if(ui.twSubscriptions->topLevelItem(i)->text(0) == rootName) { topItemIdx = i; break; } if(topItemIdx >= 0) { ui.twSubscriptions->scrollToItem(ui.twSubscriptions->topLevelItem(topItemIdx), QAbstractItemView::ScrollHint::PositionAtTop); } } /*! *\brief called when any option of the client (host, port, id, etc.) is changed before connecting to the broker, * checks if every option needed for the connection is set, if it is, then enables the connect button */ void ImportFileWidget::checkConnectEnable() { bool authenticationUsed = ui.chbAuthentication->isChecked(); bool idUsed = ui.chbID->isChecked(); bool authenticationFilled = !ui.leUsername->text().isEmpty() && !ui.lePassword->text().isEmpty(); bool idFilled = !ui.leID->text().isEmpty(); bool authenticationOK = !authenticationUsed || (authenticationUsed && authenticationFilled); bool idOK = !idUsed || (idUsed && idFilled); bool hostOK = !ui.leHost->text().isEmpty(); bool portOK = !ui.lePort->text().isEmpty(); bool enable = authenticationOK && idOK && hostOK && portOK; ui.bConnect->setEnabled(enable); } /*! *\brief called when m_connectTimeoutTimer ticks, * meaning that the client couldn't connect to the broker in 5 seconds * disconnects the client, stops the timer, and warns the user */ void ImportFileWidget::mqttConnectTimeout() { m_client->disconnectFromHost(); m_connectTimeoutTimer->stop(); QMessageBox::warning(this, "Warning", "Couldn't connect to the given broker"); } #endif diff --git a/src/kdefrontend/datasources/MQTTErrorWidget.cpp b/src/kdefrontend/datasources/MQTTErrorWidget.cpp index f01219b43..8913d975e 100644 --- a/src/kdefrontend/datasources/MQTTErrorWidget.cpp +++ b/src/kdefrontend/datasources/MQTTErrorWidget.cpp @@ -1,117 +1,117 @@ /*************************************************************************** File : MQTTErrorWidget.cpp Project : LabPlot Description : Widget for informing about an MQTT error, and for trying to solve it -------------------------------------------------------------------- Copyright : (C) 2018 by Kovacs Ferencz (kferike98@gmail.com) ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ #include "src/kdefrontend/datasources/MQTTErrorWidget.h" #ifdef HAVE_MQTT #include #include #include #include MQTTErrorWidget::MQTTErrorWidget(QMqttClient::ClientError error, MQTTClient* client, QWidget *parent) : QWidget(parent), m_client(client), m_error(error) { ui.setupUi(this); bool close = false; //showing the appropriate options according to the error type switch (m_error) { case QMqttClient::ClientError::IdRejected: ui.lePassword->hide(); ui.lPassword->hide(); ui.leUserName->hide(); ui.lUserName->hide(); ui.lErrorType->setText("The client ID is malformed. This might be related to its length.\nSet new ID!"); break; case QMqttClient::ClientError::BadUsernameOrPassword: ui.lId->hide(); ui.leId->hide(); ui.lErrorType->setText("The data in the username or password is malformed.\nSet new username and password!"); break; case QMqttClient::ClientError::NotAuthorized: ui.lId->hide(); ui.leId->hide(); ui.lErrorType->setText("The client is not authorized to connect."); break; case QMqttClient::ClientError::ServerUnavailable: ui.lePassword->hide(); ui.lPassword->hide(); ui.leUserName->hide(); ui.lUserName->hide(); ui.lErrorType->setText("The network connection has been established, but the service is unavailable on the broker side."); break; case QMqttClient::ClientError::UnknownError: ui.lePassword->hide(); ui.lPassword->hide(); ui.leUserName->hide(); ui.lUserName->hide(); ui.lErrorType->setText("An unknown error occurred."); break; default: close = true; break; } connect(ui.bChange, &QPushButton::clicked, this, &MQTTErrorWidget::tryToReconnect); setAttribute(Qt::WA_DeleteOnClose); if(close) this->close(); } /*! *\brief Try to reconnect in MQTTClient after reseting options that might cause the error */ void MQTTErrorWidget::tryToReconnect(){ bool ok = false; switch (m_error) { case QMqttClient::ClientError::IdRejected: if(!ui.leId->text().isEmpty()) { - m_client->setMqttClientId(ui.leId->text()); + m_client->setMQTTClientId(ui.leId->text()); m_client->read(); ok = true; } break; case QMqttClient::ClientError::BadUsernameOrPassword: if(!ui.lePassword->text().isEmpty() && !ui.leUserName->text().isEmpty()) { - m_client->setMqttClientAuthentication(ui.leUserName->text(), ui.lePassword->text()); + m_client->setMQTTClientAuthentication(ui.leUserName->text(), ui.lePassword->text()); m_client->read(); ok = true; } break; case QMqttClient::ClientError::NotAuthorized: if(!ui.lePassword->text().isEmpty() && !ui.leUserName->text().isEmpty()) { - m_client->setMqttClientAuthentication(ui.leUserName->text(), ui.lePassword->text()); + m_client->setMQTTClientAuthentication(ui.leUserName->text(), ui.lePassword->text()); m_client->read(); ok = true; } break; default: break; } if (ok) this->close(); } #endif diff --git a/src/kdefrontend/dockwidgets/LiveDataDock.cpp b/src/kdefrontend/dockwidgets/LiveDataDock.cpp index 216ae45c1..7f6a9df42 100644 --- a/src/kdefrontend/dockwidgets/LiveDataDock.cpp +++ b/src/kdefrontend/dockwidgets/LiveDataDock.cpp @@ -1,1833 +1,1831 @@ /*************************************************************************** File : LiveDataDock.cpp Project : LabPlot Description : Dock widget for live data properties -------------------------------------------------------------------- Copyright : (C) 2017 by Fabian Kristof (fkristofszabolcs@gmail.com) Copyright : (C) 2018 by Kovacs Ferencz (kferike98@gmail.com) Copyright : (C) 2018 by Stefan Gerlach (stefan.gerlach@uni.kn) ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ #include "LiveDataDock.h" #include #include #include #include #include #include #ifdef HAVE_MQTT #include #endif LiveDataDock::LiveDataDock(QWidget* parent) : QWidget(parent), #ifdef HAVE_MQTT m_searching(true), m_previousMQTTClient(nullptr), m_searchTimer(new QTimer()), m_interpretMessage(true), #endif m_paused(false) { ui.setupUi(this); ui.bUpdateNow->setIcon(QIcon::fromTheme(QLatin1String("view-refresh"))); connect(ui.bPausePlayReading, &QPushButton::clicked, this, &LiveDataDock::pauseContinueReading); connect(ui.bUpdateNow, &QPushButton::clicked, this, &LiveDataDock::updateNow); connect(ui.sbUpdateInterval, static_cast(&QSpinBox::valueChanged), this, &LiveDataDock::updateIntervalChanged); connect(ui.sbKeepNValues, static_cast(&QSpinBox::valueChanged), this, &LiveDataDock::keepNValuesChanged); connect(ui.sbSampleSize, static_cast(&QSpinBox::valueChanged), this, &LiveDataDock::sampleSizeChanged); connect(ui.cbUpdateType, static_cast(&QComboBox::currentIndexChanged), this, &LiveDataDock::updateTypeChanged); connect(ui.cbReadingType, static_cast(&QComboBox::currentIndexChanged), this, &LiveDataDock::readingTypeChanged); #ifdef HAVE_MQTT m_searchTimer->setInterval(10000); connect(this, &LiveDataDock::newTopic, this, &LiveDataDock::setTopicCompleter); connect(m_searchTimer, &QTimer::timeout, this, &LiveDataDock::topicTimeout); connect(ui.bSubscribe, &QPushButton::clicked, this, &LiveDataDock::addSubscription); connect(ui.bUnsubscribe, &QPushButton::clicked, this, &LiveDataDock::removeSubscription); connect(ui.chbWill, &QCheckBox::stateChanged, this, &LiveDataDock::useWillMessage); connect(ui.cbWillQoS, static_cast(&QComboBox::currentIndexChanged), this, &LiveDataDock::willQoSChanged); connect(ui.chbWillRetain, &QCheckBox::stateChanged, this, &LiveDataDock::willRetainChanged); connect(ui.cbWillTopic, &QComboBox::currentTextChanged, this, &LiveDataDock::willTopicChanged); connect(ui.cbWillMessageType, static_cast(&QComboBox::currentIndexChanged), this, &LiveDataDock::willMessageTypeChanged); connect(ui.leWillOwnMessage, &QLineEdit::textChanged, this, &LiveDataDock::willOwnMessageChanged); connect(ui.cbWillUpdate, static_cast(&QComboBox::currentIndexChanged), this, &LiveDataDock::willUpdateTypeChanged); connect(ui.bWillUpdateNow, &QPushButton::clicked, this, &LiveDataDock::willUpdateNow); connect(ui.leWillUpdateInterval, &QLineEdit::textChanged, this, &LiveDataDock::willUpdateIntervalChanged); connect(ui.lwWillStatistics, &QListWidget::itemChanged, this, &LiveDataDock::statisticsChanged); connect(ui.leTopics, &QLineEdit::textChanged, this, &LiveDataDock::scrollToTopicTreeItem); connect(ui.leSubscriptions, &QLineEdit::textChanged, this, &LiveDataDock::scrollToSubsriptionTreeItem); ui.bSubscribe->setIcon(ui.bSubscribe->style()->standardIcon(QStyle::SP_ArrowRight)); ui.bUnsubscribe->setIcon(ui.bUnsubscribe->style()->standardIcon(QStyle::SP_BrowserStop)); #endif } LiveDataDock::~LiveDataDock() { delete m_searchTimer; QMapIterator clients(m_clients); while(clients.hasNext()) { clients.next(); delete clients.value(); } } #ifdef HAVE_MQTT /*! * \brief Sets the MQTTClients of this dock widget * \param clients */ void LiveDataDock::setMQTTClients(const QList &clients) { m_liveDataSources.clear(); m_mqttClients.clear(); m_mqttClients = clients; const MQTTClient* const fds = clients.at(0); ui.sbUpdateInterval->setValue(fds->updateInterval()); ui.cbUpdateType->setCurrentIndex(static_cast(fds->updateType())); ui.cbReadingType->setCurrentIndex(static_cast(fds->readingType())); if (fds->updateType() == MQTTClient::UpdateType::NewData) { ui.lUpdateInterval->hide(); ui.sbUpdateInterval->hide(); } if (fds->isPaused()) { ui.bPausePlayReading->setText(i18n("Continue reading")); ui.bPausePlayReading->setIcon(QIcon::fromTheme(QLatin1String("media-record"))); } else { ui.bPausePlayReading->setText(i18n("Pause reading")); ui.bPausePlayReading->setIcon(QIcon::fromTheme(QLatin1String("media-playback-pause"))); } ui.sbKeepNValues->setValue(fds->keepNValues()); + ui.sbKeepNValues->setEnabled(true); if (fds->readingType() == MQTTClient::ReadingType::TillEnd) { ui.lSampleSize->hide(); ui.sbSampleSize->hide(); } else ui.sbSampleSize->setValue(fds->sampleSize()); // disable "whole file" when having no file (i.e. socket or port) const QStandardItemModel* model = qobject_cast(ui.cbReadingType->model()); QStandardItem* item = model->item(LiveDataSource::ReadingType::WholeFile); item->setFlags(item->flags() & ~(Qt::ItemIsSelectable | Qt::ItemIsEnabled)); ui.chbWill->hide(); ui.chbWillRetain->hide(); ui.cbWillQoS->hide(); ui.cbWillMessageType->hide(); ui.cbWillTopic->hide(); ui.cbWillUpdate->hide(); ui.leWillOwnMessage->hide(); ui.leWillUpdateInterval->setValidator(new QIntValidator(2, 1000000) ); ui.leWillUpdateInterval->hide(); ui.lWillMessageType->hide(); ui.lWillOwnMessage->hide(); ui.lWillQos->hide(); ui.lWillTopic->hide(); ui.lWillUpdate->hide(); ui.lWillUpdateInterval->hide(); ui.bWillUpdateNow->hide(); ui.lwWillStatistics->hide(); ui.lWillStatistics->hide(); //show MQTT connected options ui.gbManageSubscriptions->show(); ui.bSubscribe->show(); ui.bUnsubscribe->show(); ui.twTopics->show(); ui.leTopics->show(); ui.lTopicSearch->show(); ui.twSubscriptions->show(); ui.lQoS->show(); ui.cbQoS->show(); ui.chbWill->show(); //if there isn't a client with this hostname we instantiate a new one if(m_clients[fds->clientHostName()] == nullptr) { m_clients[fds->clientHostName()] = new QMqttClient(); connect(fds, &MQTTClient::clientAboutToBeDeleted, this, &LiveDataDock::removeClient); connect(m_clients[fds->clientHostName()], &QMqttClient::connected, this, &LiveDataDock::onMQTTConnect); connect(m_clients[fds->clientHostName()], &QMqttClient::messageReceived, this, &LiveDataDock::mqttMessageReceived); m_clients[fds->clientHostName()]->setHostname(fds->clientHostName()); m_clients[fds->clientHostName()]->setPort(fds->clientPort()); - if(fds->mqttUseAuthentication()) { + if(fds->MQTTUseAuthentication()) { m_clients[fds->clientHostName()]->setUsername(fds->clientUserName()); m_clients[fds->clientHostName()]->setPassword(fds->clientPassword()); } - if(fds->mqttUseID()) { + if(fds->MQTTUseID()) { m_clients[fds->clientHostName()]->setClientId(fds->clientID()); } m_clients[fds->clientHostName()]->connectToHost(); } if(m_previousMQTTClient == nullptr) { - connect(fds, &MQTTClient::mqttSubscribed, this, &LiveDataDock::fillSubscriptions); - connect(fds, &MQTTClient::mqttTopicsChanged, this, &LiveDataDock::updateWillTopics); + connect(fds, &MQTTClient::MQTTSubscribed, this, &LiveDataDock::fillSubscriptions); + connect(fds, &MQTTClient::MQTTTopicsChanged, this, &LiveDataDock::updateWillTopics); } //if the previous MQTTClient's host name was different from the current one we have to disconnect some slots //and clear the tree widgets else if(m_previousMQTTClient->clientHostName() != fds->clientHostName()) { qDebug()<<"At load host name not equal: "<clientHostName()<<" "<clientHostName(); - disconnect(m_previousMQTTClient, &MQTTClient::mqttSubscribed, this, &LiveDataDock::fillSubscriptions); - disconnect(m_previousMQTTClient, &MQTTClient::mqttTopicsChanged, this, &LiveDataDock::updateWillTopics); + disconnect(m_previousMQTTClient, &MQTTClient::MQTTSubscribed, this, &LiveDataDock::fillSubscriptions); + disconnect(m_previousMQTTClient, &MQTTClient::MQTTTopicsChanged, this, &LiveDataDock::updateWillTopics); disconnect(m_clients[m_previousMQTTClient->clientHostName()], &QMqttClient::messageReceived, this, &LiveDataDock::mqttMessageReceived); connect(m_clients[m_previousMQTTClient->clientHostName()], &QMqttClient::messageReceived, this, &LiveDataDock::mqttMessageReceivedInBackground); disconnect(m_clients[fds->clientHostName()], &QMqttClient::messageReceived, this, &LiveDataDock::mqttMessageReceivedInBackground); ui.twTopics->clear(); //repopulating the tree widget with the already known topics of the client for(int i = 0; i < m_addedTopics[fds->clientHostName()].size(); ++i) { addTopicToTree(m_addedTopics[fds->clientHostName()].at(i)); } //fill subscriptions tree widget ui.twSubscriptions->clear(); fillSubscriptions(); - connect(fds, &MQTTClient::mqttSubscribed, this, &LiveDataDock::fillSubscriptions); - connect(fds, &MQTTClient::mqttTopicsChanged, this, &LiveDataDock::updateWillTopics); + connect(fds, &MQTTClient::MQTTSubscribed, this, &LiveDataDock::fillSubscriptions); + connect(fds, &MQTTClient::MQTTTopicsChanged, this, &LiveDataDock::updateWillTopics); connect(m_clients[fds->clientHostName()], &QMqttClient::messageReceived, this, &LiveDataDock::mqttMessageReceived); } //set will message connected options updateWillTopics(); ui.leWillOwnMessage->setText(fds->willOwnMessage()); ui.leWillUpdateInterval->setText(QString::number(fds->willTimeInterval())); ui.cbWillUpdate->setCurrentIndex(static_cast(fds->willUpdateType()) ); fds->startWillTimer(); ui.cbWillMessageType->setCurrentIndex(static_cast(fds->willMessageType()) ); ui.cbWillQoS->setCurrentIndex(fds->willQoS()); ui.cbWillTopic->setCurrentText(fds->willTopic()); ui.chbWillRetain->setChecked(fds->willRetain()); QVector statitics = fds->willStatistics(); for(int i = 0; i < statitics.count(); ++i) { QListWidgetItem* item = ui.lwWillStatistics->item(static_cast(i)); if(statitics[i]) { item->setCheckState(Qt::Checked); } else { item->setCheckState(Qt::Unchecked); } } //when chbWill's isChecked corresponds with source's m_mqttWillUse it doesn't emit state changed signal, we have to force it - bool checked = fds->mqttWillUse(); + bool checked = fds->MQTTWillUse(); ui.chbWill->setChecked(!checked); ui.chbWill->setChecked(checked); m_previousMQTTClient = fds; } #endif /*! * \brief Sets the live data sources of this dock widget * \param sources */ void LiveDataDock::setLiveDataSources(const QList& sources) { #ifdef HAVE_MQTT m_mqttClients.clear(); #endif m_liveDataSources = sources; const LiveDataSource* const fds = sources.at(0); const LiveDataSource::SourceType sourceType = fds->sourceType(); const LiveDataSource::ReadingType readingType = fds->readingType(); const LiveDataSource::UpdateType updateType = fds->updateType(); ui.sbUpdateInterval->setValue(fds->updateInterval()); ui.cbUpdateType->setCurrentIndex(static_cast(updateType)); ui.cbReadingType->setCurrentIndex(static_cast(readingType)); if (updateType == LiveDataSource::UpdateType::NewData) { ui.lUpdateInterval->hide(); ui.sbUpdateInterval->hide(); } if (fds->isPaused()) { ui.bPausePlayReading->setText(i18n("Continue Reading")); ui.bPausePlayReading->setIcon(QIcon::fromTheme(QLatin1String("media-record"))); } else { ui.bPausePlayReading->setText(i18n("Pause Reading")); ui.bPausePlayReading->setIcon(QIcon::fromTheme(QLatin1String("media-playback-pause"))); } ui.sbKeepNValues->setValue(fds->keepNValues()); // disable "whole file" when having no file (i.e. socket or port) const QStandardItemModel* model = qobject_cast(ui.cbReadingType->model()); QStandardItem* item = model->item(LiveDataSource::ReadingType::WholeFile); if (sourceType == LiveDataSource::SourceType::FileOrPipe) item->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled); else item->setFlags(item->flags() & ~(Qt::ItemIsSelectable | Qt::ItemIsEnabled)); if (sourceType == LiveDataSource::SourceType::NetworkTcpSocket || sourceType == LiveDataSource::SourceType::LocalSocket || sourceType == LiveDataSource::SourceType::SerialPort || readingType == LiveDataSource::ReadingType::TillEnd || readingType == LiveDataSource::ReadingType::WholeFile) { ui.lSampleSize->hide(); ui.sbSampleSize->hide(); } else { ui.sbSampleSize->setValue(fds->sampleSize()); } // disable "on new data"-option if not available model = qobject_cast(ui.cbUpdateType->model()); item = model->item(LiveDataSource::UpdateType::NewData); if (sourceType == LiveDataSource::SourceType::NetworkTcpSocket || sourceType == LiveDataSource::SourceType::NetworkUdpSocket || sourceType == LiveDataSource::SourceType::SerialPort) { item->setFlags(item->flags() & ~(Qt::ItemIsSelectable | Qt::ItemIsEnabled)); } else { item->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled); } ui.chbWill->hide(); ui.chbWillRetain->hide(); ui.cbWillQoS->hide(); ui.cbWillMessageType->hide(); ui.cbWillTopic->hide(); ui.cbWillUpdate->hide(); ui.leWillOwnMessage->hide(); ui.leWillUpdateInterval->setValidator(new QIntValidator(2, 1000000) ); ui.leWillUpdateInterval->hide(); ui.lWillMessageType->hide(); ui.lWillOwnMessage->hide(); ui.lWillQos->hide(); ui.lWillTopic->hide(); ui.lWillUpdate->hide(); ui.lWillUpdateInterval->hide(); ui.bWillUpdateNow->hide(); ui.lwWillStatistics->hide(); ui.lWillStatistics->hide(); ui.bSubscribe->hide(); ui.bUnsubscribe->hide(); ui.twTopics->hide(); ui.leTopics->hide(); ui.lTopicSearch->hide(); ui.twSubscriptions->hide(); ui.gbManageSubscriptions->hide(); ui.lQoS->hide(); ui.cbQoS->hide(); } /*! * \brief Modifies the sample size of the live data sources or MQTTClient objects * \param sampleSize */ void LiveDataDock::sampleSizeChanged(int sampleSize) { if(!m_liveDataSources.isEmpty()) { for (auto* source : m_liveDataSources) source->setSampleSize(sampleSize); } #ifdef HAVE_MQTT else if (!m_mqttClients.isEmpty()) { for (auto* client : m_mqttClients) client->setSampleSize(sampleSize); } #endif } /*! * \brief Updates the live data sources now */ void LiveDataDock::updateNow() { if(!m_liveDataSources.isEmpty()) { for (auto* source : m_liveDataSources) source->updateNow(); } #ifdef HAVE_MQTT else if (!m_mqttClients.isEmpty()) { for (auto* client : m_mqttClients) client->updateNow(); } #endif } /*! * \brief LiveDataDock::updateTypeChanged * \param idx */ void LiveDataDock::updateTypeChanged(int idx) { if(!m_liveDataSources.isEmpty()) { DEBUG("LiveDataDock::updateTypeChanged()"); const LiveDataSource::UpdateType type = static_cast(idx); switch (type) { case LiveDataSource::UpdateType::TimeInterval: ui.lUpdateInterval->show(); ui.sbUpdateInterval->show(); ui.lSampleSize->show(); ui.sbSampleSize->show(); for (auto* source: m_liveDataSources) { source->setUpdateType(type); source->setUpdateInterval(ui.sbUpdateInterval->value()); source->setFileWatched(false); } break; case LiveDataSource::UpdateType::NewData: ui.lUpdateInterval->hide(); ui.sbUpdateInterval->hide(); ui.lSampleSize->hide(); ui.sbSampleSize->hide(); for (auto* source: m_liveDataSources) { source->setFileWatched(true); source->setUpdateType(type); } } } #ifdef HAVE_MQTT else if (!m_mqttClients.isEmpty()) { MQTTClient::UpdateType type = static_cast(idx); if (type == MQTTClient::UpdateType::TimeInterval) { ui.lUpdateInterval->show(); ui.sbUpdateInterval->show(); for (auto* client : m_mqttClients) { client->setUpdateType(type); client->setUpdateInterval(ui.sbUpdateInterval->value()); } } else if (type == MQTTClient::UpdateType::NewData) { ui.lUpdateInterval->hide(); ui.sbUpdateInterval->hide(); for (auto* client : m_mqttClients) { client->setUpdateType(type); } } } #endif } /*! * \brief Handles the change of the reading type in the dock widget * \param idx */ void LiveDataDock::readingTypeChanged(int idx) { if(!m_liveDataSources.isEmpty()) { const LiveDataSource::ReadingType type = static_cast(idx); const LiveDataSource* const fds = m_liveDataSources.at(0); const LiveDataSource::SourceType sourceType = fds->sourceType(); const LiveDataSource::UpdateType updateType = fds->updateType(); if (sourceType == LiveDataSource::SourceType::NetworkTcpSocket || sourceType == LiveDataSource::SourceType::LocalSocket || sourceType == LiveDataSource::SourceType::SerialPort || type == LiveDataSource::ReadingType::TillEnd || type == LiveDataSource::ReadingType::WholeFile || updateType == LiveDataSource::UpdateType::NewData) { ui.lSampleSize->hide(); ui.sbSampleSize->hide(); } else { ui.lSampleSize->show(); ui.sbSampleSize->show(); } for (auto* source : m_liveDataSources) source->setReadingType(type); } #ifdef HAVE_MQTT else if (!m_mqttClients.isEmpty()) { MQTTClient::ReadingType type = static_cast(idx); if (type == MQTTClient::ReadingType::TillEnd) { ui.lSampleSize->hide(); ui.sbSampleSize->hide(); } else { ui.lSampleSize->show(); ui.sbSampleSize->show(); } for (auto* client : m_mqttClients) client->setReadingType(type); } #endif } /*! * \brief Modifies the update interval of the live data sources * \param updateInterval */ void LiveDataDock::updateIntervalChanged(int updateInterval) { if(!m_liveDataSources.isEmpty()) { for (auto* source : m_liveDataSources) source->setUpdateInterval(updateInterval); } #ifdef HAVE_MQTT else if (!m_mqttClients.isEmpty()) { for (auto* client : m_mqttClients) client->setUpdateInterval(updateInterval); } #endif } /*! * \brief Modifies the number of samples to keep in each of the live data sources * \param keepNValues */ void LiveDataDock::keepNValuesChanged(const int keepNValues) { if(!m_liveDataSources.isEmpty()) { for (auto* source : m_liveDataSources) source->setKeepNValues(keepNValues); } #ifdef HAVE_MQTT else if (!m_mqttClients.isEmpty()) { for (auto* client : m_mqttClients) client->setKeepNValues(keepNValues); } #endif } /*! * \brief Pauses the reading of the live data source */ void LiveDataDock::pauseReading() { if(!m_liveDataSources.isEmpty()) { for (auto* source: m_liveDataSources) source->pauseReading(); } #ifdef HAVE_MQTT else if (!m_mqttClients.isEmpty()) { for (auto* client : m_mqttClients) client->pauseReading(); } #endif } /*! * \brief Continues the reading of the live data source */ void LiveDataDock::continueReading() { if(!m_liveDataSources.isEmpty()) { for (auto* source: m_liveDataSources) source->continueReading(); } #ifdef HAVE_MQTT else if (!m_mqttClients.isEmpty()) { for (auto* client : m_mqttClients) client->continueReading(); } #endif } /*! * \brief Handles the pausing/continuing of reading of the live data source */ void LiveDataDock::pauseContinueReading() { m_paused = !m_paused; if (m_paused) { pauseReading(); ui.bPausePlayReading->setText(i18n("Continue Reading")); ui.bPausePlayReading->setIcon(QIcon::fromTheme(QLatin1String("media-record"))); } else { continueReading(); ui.bPausePlayReading->setText(i18n("Pause Reading")); ui.bPausePlayReading->setIcon(QIcon::fromTheme(QLatin1String("media-playback-pause"))); } } #ifdef HAVE_MQTT /*! *\brief called when use will message checkbox's state is changed, *if state is checked it shows the options regarding the will message * and also sets the mqttUseWill according to state for every client in m_mqttClients * * \param state the state of the checbox */ void LiveDataDock::useWillMessage(int state) { qDebug()<<"will checkstate changed" <setMqttWillUse(true); + source->setMQTTWillUse(true); ui.chbWillRetain->show(); ui.cbWillQoS->show(); ui.cbWillMessageType->show(); ui.cbWillTopic->show(); ui.cbWillUpdate->show(); ui.lWillMessageType->show(); ui.lWillQos->hide(); ui.lWillTopic->show(); ui.lWillUpdate->show(); if (ui.cbWillMessageType->currentIndex() == (int)MQTTClient::WillMessageType::OwnMessage) { ui.leWillOwnMessage->show(); ui.lWillOwnMessage->show(); } else if(ui.cbWillMessageType->currentIndex() == (int)MQTTClient::WillMessageType::Statistics){ ui.lWillStatistics->show(); ui.lwWillStatistics->show(); } if(ui.cbWillUpdate->currentIndex() == static_cast(MQTTClient::WillUpdateType::TimePeriod)) { ui.leWillUpdateInterval->show(); ui.lWillUpdateInterval->show(); } else if (ui.cbWillUpdate->currentIndex() == static_cast(MQTTClient::WillUpdateType::OnClick)) ui.bWillUpdateNow->show(); } else if (state == Qt::Unchecked) { for (auto* source: m_mqttClients) - source->setMqttWillUse(false); + source->setMQTTWillUse(false); ui.chbWillRetain->hide(); ui.cbWillQoS->hide(); ui.cbWillMessageType->hide(); ui.cbWillTopic->hide(); ui.cbWillUpdate->hide(); ui.leWillOwnMessage->hide(); ui.leWillUpdateInterval->hide(); ui.lWillMessageType->hide(); ui.lWillOwnMessage->hide(); ui.lWillQos->hide(); ui.lWillTopic->hide(); ui.lWillUpdate->hide(); ui.lWillUpdateInterval->hide(); ui.bWillUpdateNow->hide(); ui.lWillStatistics->hide(); ui.lwWillStatistics->hide(); } } /*! *\brief called when will message's QoS is changed * sets the will QoS level for every client in m_mqttClients * * \param QoS the QoS level of the will message */ void LiveDataDock::willQoSChanged(int QoS) { for (auto* source: m_mqttClients) source->setWillQoS(QoS); } /*! *\brief called when will message's retain flag is changed * sets the retain flag for the will message in every client in m_mqttClients * * \param state the state of the will retain chechbox */ void LiveDataDock::willRetainChanged(int state) { if(state == Qt::Checked) { for (auto* source: m_mqttClients) source->setWillRetain(true); } else if (state == Qt::Unchecked) { for (auto* source: m_mqttClients) source->setWillRetain(false); } } /*! *\brief called when will topic combobox's current item is changed * sets the will topic for every client in m_mqttClients * * \param topic the current text of cbWillTopic */ void LiveDataDock::willTopicChanged(const QString& topic) { qDebug()<<"topic changed" << topic; for (auto* source: m_mqttClients) { if(source->willTopic() != topic) source->clearLastMessage(); source->setWillTopic(topic); } } /*! *\brief called when the selected will message type is changed, * shows the options for the selected message type, hides the irrelevant onesd * sets the will message type for every client in m_mqttClients * * \param type the selected will message type */ void LiveDataDock::willMessageTypeChanged(int type) { for (auto* source: m_mqttClients) source->setWillMessageType(static_cast (type)); if(static_cast (type) == MQTTClient::WillMessageType::OwnMessage) { ui.leWillOwnMessage->show(); ui.lWillOwnMessage->show(); ui.lWillStatistics->hide(); ui.lwWillStatistics->hide(); } else if(static_cast (type) == MQTTClient::WillMessageType::LastMessage) { ui.leWillOwnMessage->hide(); ui.lWillOwnMessage->hide(); ui.lWillStatistics->hide(); ui.lwWillStatistics->hide(); } else if(static_cast (type) == MQTTClient::WillMessageType::Statistics) { ui.lWillStatistics->show(); ui.lwWillStatistics->show(); ui.leWillOwnMessage->hide(); ui.lWillOwnMessage->hide(); } } /*! *\brief called when the will own message is changed * sets the will own message for every client in m_mqttClients * * \param message the will message given by the user */ void LiveDataDock::willOwnMessageChanged(const QString& message) { for (auto* source: m_mqttClients) source->setWillOwnMessage(message); } /*! *\brief called when the mqttTopicsChanged signal of a MQTTClient from m_mqttClients is emitted * updates the content of the cbWillTopic with every topic belonging to the MQTTClient */ void LiveDataDock::updateWillTopics() { ui.cbWillTopic->clear(); const MQTTClient* const fds = m_mqttClients.at(0); QVector topics = fds->topicNames(); if(!topics.isEmpty()) { for(int i = 0; i < topics.count(); i++) { qDebug()<<"Live Data Dock: updating will topics: "<addItem(topics[i]); } if(!fds->willTopic().isEmpty()) ui.cbWillTopic->setCurrentText(fds->willTopic()); } else qDebug()<<"Topic Vector Empty"; } /*! *\brief called when the selected update type for the will message is changed, * shows the options for the selected update type, hides the irrelevant ones * sets the will update type for every client in m_mqttClients * * \param type the selected will update type */ void LiveDataDock::willUpdateTypeChanged(int updateType) { qDebug()<<"Update type changed" << updateType; for (auto* source: m_mqttClients) source->setWillUpdateType(static_cast(updateType)); if(static_cast(updateType) == MQTTClient::WillUpdateType::TimePeriod) { ui.bWillUpdateNow->hide(); ui.leWillUpdateInterval->show(); ui.lWillUpdateInterval->show(); for (auto* source: m_mqttClients) { source->setWillTimeInterval(ui.leWillUpdateInterval->text().toInt()); source->startWillTimer(); } } else if (static_cast(updateType) == MQTTClient::WillUpdateType::OnClick) { ui.bWillUpdateNow->show(); ui.leWillUpdateInterval->hide(); ui.lWillUpdateInterval->hide(); //if update type is on click we stop the will timer for (auto* source: m_mqttClients) source->stopWillTimer(); } } /*! *\brief called when the will update now button is pressed * updates the will message of every client in m_mqttClients */ void LiveDataDock::willUpdateNow() { for (auto* source: m_mqttClients) source->updateWillMessage(); } /*! *\brief called when the update interval for will message is changed, * sets the will update interval for every client in m_mqttClients, then starts the will timer for each one * * \param interval the new will update interval */ void LiveDataDock::willUpdateIntervalChanged(const QString& interval) { qDebug()<<"Update interval changed " <setWillTimeInterval(interval.toInt()); source->startWillTimer(); } } /*! *\brief called when the check state of a ListWidget item is changed * adds or removes the statistic represented by the item from every client in m_mqttClients * * \param item the ListWidgetItem, the check state of which was changed */ void LiveDataDock::statisticsChanged(QListWidgetItem *item) { //determine the index of the item int idx = -1; for(int i = 0; i < ui.lwWillStatistics->count(); i++) if(item->text() == ui.lwWillStatistics->item(i)->text()) { idx = i; break; } //if it's checked we add it if(item->checkState() == Qt::Checked) { if(idx >= 0) { for (auto* source: m_mqttClients) source->addWillStatistics(static_cast(idx) ); } } //otherwise remove it else { if(idx >= 0){ for (auto* source: m_mqttClients) source->removeWillStatistics(static_cast(idx) ); } } } /*! *\brief called when the client connects to the broker succesfully, it subscribes to every topic (# wildcard) * in order to later list every available topic */ void LiveDataDock::onMQTTConnect() { QMqttTopicFilter globalFilter{"#"}; QMqttSubscription * subscription = m_clients[m_mqttClients.first()->clientHostName()]->subscribe(globalFilter, 1); if(!subscription) qDebug()<<"Couldn't make global subscription in LiveDataDock"; } /*! *\brief called when the client receives a message * if the message arrived from a new topic, the topic is put in twTopics */ void LiveDataDock::mqttMessageReceived(const QByteArray& message, const QMqttTopicName& topic) { if(!m_addedTopics[m_mqttClients.first()->clientHostName()].contains(topic.name())) { m_addedTopics[m_mqttClients.first()->clientHostName()].push_back(topic.name()); addTopicToTree(topic.name()); } } /*! *\brief called when the subscribe button is pressed * subscribes to the topic represented by the current item of twTopics in every client from m_mqttClients */ void LiveDataDock::addSubscription() { QString name; QTreeWidgetItem *item = ui.twTopics->currentItem(); if(item != nullptr) { QTreeWidgetItem *tempItem = item; //determine the topic name that the current item represents name.prepend(item->text(0)); if(item->childCount() != 0) name.append("/#"); while(tempItem->parent() != nullptr) { tempItem = tempItem->parent(); name.prepend(tempItem->text(0) + "/"); } //check if the subscription already exists QList topLevelList = ui.twSubscriptions->findItems(name, Qt::MatchExactly); if(topLevelList.isEmpty() || topLevelList.first()->parent() != nullptr) { qDebug() << name; bool foundSuperior = false; for(int i = 0; i < ui.twSubscriptions->topLevelItemCount(); ++i) { qDebug()<topLevelItemCount(); //if the new subscriptions contains an already existing one, we remove the inferior one if(checkTopicContains(name, ui.twSubscriptions->topLevelItem(i)->text(0)) && name != ui.twSubscriptions->topLevelItem(i)->text(0)) { qDebug()<<"1"<topLevelItem(i)->text(0); ui.twSubscriptions->topLevelItem(i)->takeChildren(); ui.twSubscriptions->takeTopLevelItem(i); i--; continue; } qDebug()<<"checked inferior"; //if there is a subscription containing the new one we set foundSuperior true if(checkTopicContains(ui.twSubscriptions->topLevelItem(i)->text(0), name) && name != ui.twSubscriptions->topLevelItem(i)->text(0)) { foundSuperior = true; qDebug()<<"2"<topLevelItem(i)->text(0); break; } qDebug()<<"checked superior"; } //if there wasn't a superior subscription we can subscribe to the new topic if(!foundSuperior) { qDebug()<<"Adding new topic"; QStringList toplevelName; toplevelName.push_back(name); QTreeWidgetItem* newTopLevelItem = new QTreeWidgetItem(toplevelName); ui.twSubscriptions->addTopLevelItem(newTopLevelItem); if(name.endsWith("#")) { //adding every topic that the subscription contains to twSubscriptions addSubscriptionChildren(item, newTopLevelItem); } //subscribe in every MQTTClient for (auto* source: m_mqttClients) { source->addMQTTSubscription(name, ui.cbQoS->currentIndex()); } if(name.endsWith("#")) { //if an already existing subscription contains a topic that the new subscription also contains //we decompose the already existing subscription //by unsubscribing from its topics, that are present in the new subscription as well QStringList nameList = name.split('/', QString::SkipEmptyParts); QString root = nameList.first(); QVector children; for(int i = 0; i < ui.twSubscriptions->topLevelItemCount(); ++i) { if(ui.twSubscriptions->topLevelItem(i)->text(0).startsWith(root) && name != ui.twSubscriptions->topLevelItem(i)->text(0)) { children.clear(); //get the "leaf" children of the inspected subscription findSubscriptionLeafChildren(children, ui.twSubscriptions->topLevelItem(i)); for(int j = 0; j < children.size(); ++j) { if(checkTopicContains(name, children[j]->text(0))) { - qDebug()<text(0); - //if the new subscription contains a topic, we unsubscribe from it QTreeWidgetItem* unsubscribeItem = children[j]; while(unsubscribeItem->parent() != nullptr) { for(int i = 0; i < unsubscribeItem->parent()->childCount(); ++i) { - qDebug()<parent()->childCount(); if(unsubscribeItem->text(0) != unsubscribeItem->parent()->child(i)->text(0)) { //add topic as subscription to every client for (auto* source: m_mqttClients) { source->addBeforeRemoveSubscription(unsubscribeItem->parent()->child(i)->text(0), ui.cbQoS->currentIndex()); } //also add it to twSubscripitons ui.twSubscriptions->addTopLevelItem(unsubscribeItem->parent()->takeChild(i)); i--; } else { //before we remove the topic, we reparent it to the new subscription //so no data is lost for (auto* source: m_mqttClients) { source->reparentTopic(unsubscribeItem->text(0), name); } } } unsubscribeItem = unsubscribeItem->parent(); } qDebug()<<"Remove: "<text(0); //Remove topic/subscription for (auto* source: m_mqttClients) { source->removeMQTTSubscription(unsubscribeItem->text(0)); } ui.twSubscriptions->takeTopLevelItem(ui.twSubscriptions->indexOfTopLevelItem(unsubscribeItem)); } } } } } manageCommonLevelSubscriptions(); updateSubscriptionCompleter(); } else { QMessageBox::warning(this, "Warning", "You already subscribed to a topic containing this one"); } } else QMessageBox::warning(this, "Warning", "You already subscribed to this topic"); } else QMessageBox::warning(this, "Warning", "You didn't select any item from the Tree Widget"); } /*! *\brief called when the unsubscribe button is pressed * unsubscribes from the topic represented by the current item of twSubscription in every client from m_mqttClients */ void LiveDataDock::removeSubscription() { QTreeWidgetItem* unsubscribeItem = ui.twSubscriptions->currentItem(); if(unsubscribeItem != nullptr) { //if it is a top level item, meaning a topic that we really subscribed to(not one that belongs to a subscription) //we can simply unsubscribe from it if(unsubscribeItem->parent() == nullptr) { for (auto* source: m_mqttClients) { source->removeMQTTSubscription(unsubscribeItem->text(0)); } ui.twSubscriptions->takeTopLevelItem(ui.twSubscriptions->indexOfTopLevelItem(unsubscribeItem)); } //otherwise we remove the selected item, but subscribe to every other topic, that was contained by //the selected item's parent subscription(top level item of twSubscripitons) else{ while(unsubscribeItem->parent() != nullptr) { for(int i = 0; i < unsubscribeItem->parent()->childCount(); ++i) { if(unsubscribeItem->text(0) != unsubscribeItem->parent()->child(i)->text(0)) { //add topic as subscription to every client for (auto* source: m_mqttClients) { source->addBeforeRemoveSubscription(unsubscribeItem->parent()->child(i)->text(0), ui.cbQoS->currentIndex()); } ui.twSubscriptions->addTopLevelItem(unsubscribeItem->parent()->takeChild(i)); i--; } } unsubscribeItem = unsubscribeItem->parent(); } //remove topic/subscription from every client for (auto* source: m_mqttClients) { source->removeMQTTSubscription(unsubscribeItem->text(0)); } ui.twSubscriptions->takeTopLevelItem(ui.twSubscriptions->indexOfTopLevelItem(unsubscribeItem)); //check if any common topics were subscribed, if possible merge them manageCommonLevelSubscriptions(); } updateSubscriptionCompleter(); } else QMessageBox::warning(this, "Warning", "You didn't select any item from the Tree Widget"); } /*! *\brief called when a new topic is added to the tree(twTopics) * appends the topic's root to the topicList if it isn't in the list already * then sets the completer for leTopics */ void LiveDataDock::setTopicCompleter(const QString& topicName) { if(!m_searching) { QStringList list = topicName.split("/", QString::SkipEmptyParts); QString topic; if(!list.isEmpty()) { topic = list.at(0); } else topic = topicName; if(!m_topicList[m_mqttClients.first()->clientHostName()].contains(topic)) { m_topicList[m_mqttClients.first()->clientHostName()].append(topic); m_topicCompleter = new QCompleter(m_topicList[m_mqttClients.first()->clientHostName()], this); m_topicCompleter->setCompletionMode(QCompleter::PopupCompletion); m_topicCompleter->setCaseSensitivity(Qt::CaseSensitive); ui.leTopics->setCompleter(m_topicCompleter); } } } /*! *\brief Updates the completer for leSubscriptions */ void LiveDataDock::updateSubscriptionCompleter() { QStringList subscriptionList; - QVector subscriptions = m_mqttClients.first()->mqttSubscriptions(); + QVector subscriptions = m_mqttClients.first()->MQTTSubscriptions(); if(!subscriptions.isEmpty()) { for(int i = 0; i < subscriptions.size(); ++i) { subscriptionList.append(subscriptions[i]); } m_subscriptionCompleter = new QCompleter(subscriptionList, this); m_subscriptionCompleter->setCompletionMode(QCompleter::PopupCompletion); m_subscriptionCompleter->setCaseSensitivity(Qt::CaseSensitive); ui.leSubscriptions->setCompleter(m_subscriptionCompleter); } else { ui.leSubscriptions->setCompleter(0); } } /*! *\brief called when 10 seconds passed since the last time the user searched for a certain root in twTopics * enables updating the completer for le */ void LiveDataDock::topicTimeout() { qDebug()<<"lejart ido"; m_searching = false; m_searchTimer->stop(); } /*! *\brief called when a new the host name of the MQTTClients from m _mqttClients changes * or when the MQTTClients initialize their subscriptions * Fills twSubscriptions with the subscriptions of the MQTTClient */ void LiveDataDock::fillSubscriptions() { const MQTTClient* const fds = m_mqttClients.at(0); ui.twSubscriptions->clear(); - QVector subscriptions = fds->mqttSubscriptions(); + QVector subscriptions = fds->MQTTSubscriptions(); for (int i = 0; i < subscriptions.count(); ++i) { QStringList name; name.append(subscriptions[i]); bool found = false; for(int j = 0; j < ui.twSubscriptions->topLevelItemCount(); ++j) { if(ui.twSubscriptions->topLevelItem(j)->text(0) == subscriptions[i]) { found = true; break; } } if(!found) { qDebug()<<"add:" << subscriptions[i]; //Add the subscription to the tree widget QTreeWidgetItem* newItem = new QTreeWidgetItem(name); ui.twSubscriptions->addTopLevelItem(newItem); name.clear(); name = subscriptions[i].split('/', QString::SkipEmptyParts); //find the corresponding "root" item in twTopics QTreeWidgetItem* topic = nullptr; for(int j = 0; j < ui.twTopics->topLevelItemCount(); ++j) { if(ui.twTopics->topLevelItem(j)->text(0) == name[0]) { qDebug()<<"found top level topic: "<topLevelItem(j); break; } } //restore the children of the subscription if(topic != nullptr && topic->childCount() > 0) { qDebug()<<"restoring Children"; restoreSubscriptionChildren(topic, newItem, name, 1); } } } m_searching = false; } /*! *\brief Checks if a topic contains another one * * \param superior the name of a topic * \param inferior the name of a topic * \return true if superior is equal to or contains(if superior contains wildcards) inferior, * false otherwise */ bool LiveDataDock::checkTopicContains(const QString &superior, const QString &inferior) { if (superior == inferior) return true; else { if(superior.contains("/")) { QStringList superiorList = superior.split('/', QString::SkipEmptyParts); QStringList inferiorList = inferior.split('/', QString::SkipEmptyParts); //a longer topic can't contain a shorter one if(superiorList.size() > inferiorList.size()) return false; bool ok = true; for(int i = 0; i < superiorList.size(); ++i) { if(superiorList.at(i) != inferiorList.at(i)) { if((superiorList.at(i) != "+") && !(superiorList.at(i) == "#" && i == superiorList.size() - 1)) { qDebug() <start(); qDebug()<topLevelItemCount(); ++i) if(ui.twTopics->topLevelItem(i)->text(0) == rootName) { topItemIdx = i; break; } if(topItemIdx >= 0) { ui.twTopics->scrollToItem(ui.twTopics->topLevelItem(topItemIdx), QAbstractItemView::ScrollHint::PositionAtTop); } } /*! *\brief called when leSubscriptions' text is changed * if the rootName can be found in twSubscriptions, then we scroll it to the top of the tree widget * * \param rootName the current text of leSubscriptions */ void LiveDataDock::scrollToSubsriptionTreeItem(const QString& rootName) { m_searching = true; m_searchTimer->start(); int topItemIdx = -1; for(int i = 0; i < ui.twSubscriptions->topLevelItemCount(); ++i) if(ui.twSubscriptions->topLevelItem(i)->text(0) == rootName) { topItemIdx = i; break; } if(topItemIdx >= 0) { ui.twSubscriptions->scrollToItem(ui.twSubscriptions->topLevelItem(topItemIdx), QAbstractItemView::ScrollHint::PositionAtTop); } } /*! *\brief Returns the "+" wildcard containing topic name, which includes the given topic names * * \param first the name of a topic * \param second the name of a topic * \return The name of the common topic, if it exists, otherwise "" */ QString LiveDataDock::checkCommonLevel(const QString& first, const QString& second) { qDebug()< 0 && differIndex < firstList.size() -1) { for(int j = differIndex +1; j < firstList.size(); ++j) { if(firstList.at(j) != secondtList.at(j)) { differ = true; break; } } } else differ = true; if(!differ) { for(int i = 0; i < firstList.size(); ++i) { if(i != differIndex) { commonTopic.append(firstList.at(i)); } else { //we put "+" wildcard at the level where they differ commonTopic.append("+"); } if(i != firstList.size() - 1) commonTopic.append("/"); } } } } qDebug() << "Common topic: "<childCount() > 0) { for(int i = 0; i < topic->childCount(); ++i) { QTreeWidgetItem* temp = topic->child(i); QString name; //if it has children, then we add it as a # wildcrad containing topic if(topic->child(i)->childCount() > 0) { name.append(temp->text(0) + "/#"); while(temp->parent() != nullptr) { temp = temp->parent(); name.prepend(temp->text(0) + "/"); } } //if not then we simply add the topic itself else { name.append(temp->text(0)); while(temp->parent() != nullptr) { temp = temp->parent(); name.prepend(temp->text(0) + "/"); } } QStringList nameList; nameList.append(name); QTreeWidgetItem* childItem = new QTreeWidgetItem(nameList); subscription->addChild(childItem); //we use the function recursively on the given item addSubscriptionChildren(topic->child(i), childItem); } } } /*! *\brief Restores the children of a top level item in twSubscriptions if it contains wildcards * * \param topic pointer to a top level item in twTopics wich represents the root of the subscription topic * \param subscription pointer to a top level item in twSubscriptions, this is the item whose children will be restored * \param list QStringList containing the levels of the subscription topic * \param level the level's number which is being investigated */ void LiveDataDock::restoreSubscriptionChildren(QTreeWidgetItem * topic, QTreeWidgetItem * subscription, const QStringList& list, int level) { if(list[level] != "+" && list[level] != "#" && level < list.size() - 1) { for(int i = 0; i < topic->childCount(); ++i) { //if the current level isn't + or # wildcard we recursively continue with the next level if(topic->child(i)->text(0) == list[level]) { restoreSubscriptionChildren(topic->child(i), subscription, list, level + 1); break; } } } else if (list[level] == "+") { for(int i = 0; i < topic->childCount(); ++i) { //determine the name of the topic, contained by the subscription QString name; name.append(topic->child(i)->text(0)); for(int j = level + 1; j < list.size(); ++j) { name.append("/" + list[j]); } QTreeWidgetItem* temp = topic->child(i); while(temp->parent() != nullptr) { temp = temp->parent(); name.prepend(temp->text(0) + "/"); } //Add the topic as child of the subscription QStringList nameList; nameList.append(name); QTreeWidgetItem* newItem = new QTreeWidgetItem(nameList); subscription->addChild(newItem); //Continue adding children recursively to the new item restoreSubscriptionChildren(topic->child(i), newItem, list, level + 1); } } else if (list[level] == "#") { //add the children of the # wildcard containing subscription addSubscriptionChildren(topic, subscription); } } /*! *\brief Returns the index of level where the two topic names differ, if there is a common topic for them * * \param first the name of a topic * \param second the name of a topic * \return The index of the unequal level, if there is a common topic, otherwise -1 */ int LiveDataDock::commonLevelIndex(const QString& first, const QString& second) { qDebug()< 0) { for(int j = differIndex +1; j < firstList.size(); ++j) { if(firstList.at(j) != secondtList.at(j)) { differ = true; break; } } } else differ = true; if(!differ) { for(int i = 0; i < firstList.size(); ++i) { if(i != differIndex) commonTopic.append(firstList.at(i)); else commonTopic.append("+"); if(i != firstList.size() - 1) commonTopic.append("/"); } } } } //if there is a common topic we return the differIndex if(!commonTopic.isEmpty()) return differIndex; else return -1; } /*! *\brief Fills the children vector, with the root item's (twSubscriptions) leaf children (meaning no wildcard containing topics) * * \param children vector of TreeWidgetItem pointers * \param root pointer to a TreeWidgetItem of twSubscriptions */ void LiveDataDock::findSubscriptionLeafChildren(QVector& children, QTreeWidgetItem* root) { if(root->childCount() == 0) { children.push_back(root); } else { for(int i = 0; i < root->childCount(); ++i) { findSubscriptionLeafChildren(children, root->child(i)); } } } /*! *\brief Returns the amount of topics that the "+" wildcard will replace in the level position * * \param levelIdx the level currently being investigated * \param level the level where the new + wildcard will be placed * \param commonList the topic name split into levels * \param currentItem pointer to a TreeWidgetItem which represents the parent of the level * represented by levelIdx * \return returns the childCount, or -1 if some topics already represented by + wildcard have different * amount of children */ int LiveDataDock::checkCommonChildCount(int levelIdx, int level, QStringList& commonList, QTreeWidgetItem* currentItem) { //we recursively check the number of children, until we get to level-1 if(levelIdx < level - 1) { if(commonList[levelIdx] != "+") { for(int j = 0; j < currentItem->childCount(); ++j) { if(currentItem->child(j)->text(0) == commonList[levelIdx]) { //if the level isn't represented by + wildcard we simply return the amount of children of the corresponding item, recursively return checkCommonChildCount(levelIdx + 1, level, commonList, currentItem->child(j)); } } } else { int childCount = -1; bool ok = true; //otherwise we check if every + wildcard represented topic has the same number of children, recursively for(int j = 0; j < currentItem->childCount(); ++j) { int temp = checkCommonChildCount(levelIdx + 1, level, commonList, currentItem->child(j)); if((j > 0) && (temp != childCount)) { ok = false; break; } childCount = temp; } //if yes we return this number, otherwise -1 if(ok) return childCount; else return -1; } } else if (levelIdx == level - 1) { if(commonList[levelIdx] != "+") { for(int j = 0; j < currentItem->childCount(); ++j) { if(currentItem->child(j)->text(0) == commonList[levelIdx]) { //if the level isn't represented by + wildcard we simply return the amount of children of the corresponding item return currentItem->child(j)->childCount(); } } } else { int childCount = -1; bool ok = true; //otherwise we check if every + wildcard represented topic has the same number of children for(int j = 0; j < currentItem->childCount(); ++j) { if((j > 0) && (currentItem->child(j)->childCount() != childCount)) { ok = false; break; } childCount = currentItem->child(j)->childCount(); } //if yes we return this number, otherwise -1 if(ok) return childCount; else return -1; } } else if (level == 1 && levelIdx == 1) return currentItem->childCount(); return -1; } /*! *\brief We search in twSubscriptions for topics that can be represented using + wildcards, then merge them. * We do this until there are no topics to merge */ void LiveDataDock::manageCommonLevelSubscriptions() { bool foundEqual = false; do{ foundEqual = false; QMap> equalTopicsMap; QVector equalTopics; //compare the subscriptions present in the TreeWidget for(int i = 0; i < ui.twSubscriptions->topLevelItemCount() - 1; ++i) { for(int j = i + 1; j < ui.twSubscriptions->topLevelItemCount(); ++j) { qDebug()<topLevelItem(i)->text(0)<<" "<topLevelItem(j)->text(0); QString commonTopic = checkCommonLevel(ui.twSubscriptions->topLevelItem(i)->text(0), ui.twSubscriptions->topLevelItem(j)->text(0)); //if there is a common topic for the 2 compared topics, we add them to the map (using the common topic as key) if(!commonTopic.isEmpty()) { if(!equalTopicsMap[commonTopic].contains(ui.twSubscriptions->topLevelItem(i)->text(0))) { qDebug()<topLevelItem(i)->text(0); equalTopicsMap[commonTopic].push_back(ui.twSubscriptions->topLevelItem(i)->text(0)); } if(!equalTopicsMap[commonTopic].contains(ui.twSubscriptions->topLevelItem(j)->text(0))) { qDebug()<topLevelItem(i)->text(0); equalTopicsMap[commonTopic].push_back(ui.twSubscriptions->topLevelItem(j)->text(0)); } } } } if(!equalTopicsMap.isEmpty()) { qDebug()<<"Equal topics not empty"; QVector commonTopics; QMapIterator> topics(equalTopicsMap); //check for every map entry, if the found topics can be merged or not while(topics.hasNext()) { topics.next(); qDebug()<<"Checking: " << topics.key(); int level = commonLevelIndex(topics.value().last(), topics.value().first()); QStringList commonList = topics.value().first().split('/', QString::SkipEmptyParts); QTreeWidgetItem* currentItem; //search the corresponding item to the common topics first level(root) for(int i = 0; i < ui.twTopics->topLevelItemCount(); ++i) { if(ui.twTopics->topLevelItem(i)->text(0) == commonList.first()) { currentItem = ui.twTopics->topLevelItem(i); break; } } //calculate the number of topics the new + wildcard could replace int childCount = checkCommonChildCount(1, level, commonList, currentItem); if(childCount > 0) { //if the number of topics found and the calculated number of topics is equal, the topics can be merged if(topics.value().size() == childCount) { for(int k = 0; k < topics.value().size(); ++k) qDebug()<addTopLevelItem(newTopic); //remove the "merged" topics for(int i = 0; i < equalTopics.size(); ++i) { for(int j = 0; j < ui.twSubscriptions->topLevelItemCount(); ++j){ if(ui.twSubscriptions->topLevelItem(j)->text(0) == equalTopics[i]) { newTopic->addChild(ui.twSubscriptions->takeTopLevelItem(j)); break; } } } //remove any subscription that the new subscription contains for(int i = 0; i < ui.twSubscriptions->topLevelItemCount(); ++i) { if(checkTopicContains(commonTopic, ui.twSubscriptions->topLevelItem(i)->text(0)) && commonTopic != ui.twSubscriptions->topLevelItem(i)->text(0) ) { ui.twSubscriptions->topLevelItem(i)->takeChildren(); ui.twSubscriptions->takeTopLevelItem(i); i--; } } //make the subscripiton on commonTopic in every MQTTClient from m_mqttClients for (auto* source: m_mqttClients) { source->addMQTTSubscription(commonTopic, ui.cbQoS->currentIndex()); } } } } while(foundEqual); } /*! *\brief Adds topicName to twTopics * * \param topicName the name of the topic, which will be added to the tree widget */ void LiveDataDock::addTopicToTree(const QString &topicName) { QStringList name; QChar sep = '/'; QString rootName; if(topicName.contains(sep)) { QStringList list = topicName.split(sep, QString::SkipEmptyParts); if(!list.isEmpty()) { rootName = list.at(0); name.append(list.at(0)); QTreeWidgetItem* currentItem; //check whether the first level of the topic can be found in twTopics int topItemIdx = -1; for(int i = 0; i < ui.twTopics->topLevelItemCount(); ++i) { if(ui.twTopics->topLevelItem(i)->text(0) == list.at(0)) { topItemIdx = i; break; } } //if not we simply add every level of the topic to the tree if( topItemIdx < 0) { currentItem = new QTreeWidgetItem(name); ui.twTopics->addTopLevelItem(currentItem); for(int i = 1; i < list.size(); ++i) { name.clear(); name.append(list.at(i)); currentItem->addChild(new QTreeWidgetItem(name)); currentItem = currentItem->child(0); } } //otherwise we search for the first level that isn't part of the tree, //then add every level of the topic to the tree from that certain level else { currentItem = ui.twTopics->topLevelItem(topItemIdx); int listIdx = 1; for(; listIdx < list.size(); ++listIdx) { QTreeWidgetItem* childItem = nullptr; bool found = false; for(int j = 0; j < currentItem->childCount(); ++j) { childItem = currentItem->child(j); if(childItem->text(0) == list.at(listIdx)) { found = true; currentItem = childItem; break; } } if(!found) { //this is the level that isn't present in the tree break; } } //add every level to the tree starting with the first level that isn't part of the tree for(; listIdx < list.size(); ++listIdx) { name.clear(); name.append(list.at(listIdx)); currentItem->addChild(new QTreeWidgetItem(name)); currentItem = currentItem->child(currentItem->childCount() - 1); } } } } else { rootName = topicName; name.append(topicName); ui.twTopics->addTopLevelItem(new QTreeWidgetItem(name)); } //if a subscribed topic contains the new topic, we have to update twSubscriptions for(int i = 0; i < ui.twSubscriptions->topLevelItemCount(); ++i) { QStringList subscriptionName = ui.twSubscriptions->topLevelItem(i)->text(0).split('/', QString::SkipEmptyParts); if (rootName == subscriptionName[0]) { qDebug()<clientHostName()].contains(topic.name())) { m_addedTopics[m_mqttClients.first()->clientHostName()].push_back(topic.name()); } } /*! *\brief called when an MQTTClient is about to be deleted * removes every data connected to the MQTTClient, and disconnects the corresponding client from m_clients * * \param name the host name of the MQTTClient that will be deleted */ void LiveDataDock::removeClient(const QString& name) { m_clients[name]->disconnectFromHost(); m_addedTopics.remove(name); m_topicList.remove(name); if(m_previousMQTTClient != nullptr && m_previousMQTTClient->clientHostName() == name) { disconnect(m_clients[m_previousMQTTClient->clientHostName()], &QMqttClient::messageReceived, this, &LiveDataDock::mqttMessageReceivedInBackground); m_previousMQTTClient = nullptr; } if(m_mqttClients.first()->clientHostName() == name) { ui.twSubscriptions->clear(); ui.twTopics->clear(); m_mqttClients.clear(); } delete m_clients[name]; m_clients.remove(name); } /*! * \brief Used for testing the MQTT related features * \param topic */ bool LiveDataDock::testSubscribe(const QString& topic){ QStringList topicList = topic.split("/", QString::SkipEmptyParts); QTreeWidgetItem* currentItem = nullptr; for(int i = 0; i topLevelItemCount(); ++i) { if(ui.twTopics->topLevelItem(i)->text(0) == topicList[0]) { currentItem = ui.twTopics->topLevelItem(i); break; } } if (currentItem) { for(int i = 1 ; i < topicList.size(); ++i) { if(topicList[i] == "#") break; for(int j = 0; j < currentItem->childCount(); ++j) { if(currentItem->child(j)->text(0) == topicList[i]) { currentItem = currentItem->child(j); break; } else if (j == currentItem->childCount() - 1) return false; } } } else return false; ui.twTopics->setCurrentItem(currentItem); addSubscription(); return true; } /*! * \brief Used for testing the MQTT related features * \param topic */ bool LiveDataDock::testUnsubscribe(const QString& topic) { QTreeWidgetItem* currentItem = nullptr; for(int i = 0; i < ui.twSubscriptions->topLevelItemCount(); ++i) { if(checkTopicContains(ui.twSubscriptions->topLevelItem(i)->text(0), topic)) { currentItem = ui.twSubscriptions->topLevelItem(i); break; } } if(currentItem) { do { if(topic == currentItem->text(0)) { ui.twSubscriptions->setCurrentItem(currentItem); removeSubscription(); return true; } else { for(int i = 0; i < currentItem->childCount(); ++i) { qDebug()<child(i)->text(0)<<" "<child(i)->text(0), topic)) { currentItem = currentItem->child(i); break; } else if (i == currentItem->childCount() - 1) return false; } } } while(currentItem); } else return false; return false; } #endif diff --git a/tests/import_export/MQTT/MQTTUnitTest.cpp b/tests/import_export/MQTT/MQTTUnitTest.cpp index 2d9052fd5..1296c801d 100644 --- a/tests/import_export/MQTT/MQTTUnitTest.cpp +++ b/tests/import_export/MQTT/MQTTUnitTest.cpp @@ -1,599 +1,599 @@ /*************************************************************************** File : MQTTUnitTest.cpp Project : LabPlot Description : Tests for MQTT related features -------------------------------------------------------------------- Copyright : (C) 2018 Kovacs Ferencz (kferike98@gmail.com) ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ #include "MQTTUnitTest.h" #ifdef HAVE_MQTT #include "backend/datasources/filters/AsciiFilter.h" #include "backend/datasources/MQTTClient.h" #include "backend/datasources/MQTTSubscription.h" #include "backend/datasources/MQTTTopic.h" #include "backend/core/Project.h" #include "kdefrontend/dockwidgets/LiveDataDock.h" #include #include #include #include #include #include #include void MQTTUnitTest::initTestCase() { const QString currentDir = __FILE__; m_dataDir = currentDir.left(currentDir.lastIndexOf(QDir::separator())) + QDir::separator() + QLatin1String("data") + QDir::separator(); // needed in order to have the signals triggered by SignallingUndoCommand, see LabPlot.cpp //TODO: redesign/remove this qRegisterMetaType("const AbstractAspect*"); qRegisterMetaType("const AbstractColumn*"); } //############################################################################## //################### check superior and inferior relations ################## //############################################################################## void MQTTUnitTest::testContainFalse() { MQTTClient* client = new MQTTClient("test"); const QString fileName = m_dataDir + "contain_false.txt"; QFile file(fileName); if(file.open(QIODevice::ReadOnly)) { QTextStream in(&file); while(!in.atEnd()) { QString line = in.readLine(); QStringList topics = line.split(" ", QString::SkipEmptyParts); QCOMPARE(client->checkTopicContains(topics[0], topics[1]), false); } delete client; file.close(); } } void MQTTUnitTest::testContainTrue() { MQTTClient* client = new MQTTClient("test"); const QString fileName = m_dataDir + "contain_true.txt"; QFile file(fileName); if(file.open(QIODevice::ReadOnly)) { QTextStream in(&file); while(!in.atEnd()) { QString line = in.readLine(); QStringList topics = line.split(" ", QString::SkipEmptyParts); QCOMPARE(client->checkTopicContains(topics[0], topics[1]), true); } delete client; file.close(); } } //############################################################################## //############################ check common topics ########################### //############################################################################## void MQTTUnitTest::testCommonTrue(){ MQTTClient* client = new MQTTClient("test"); const QString fileName = m_dataDir + "common_true.txt"; QFile file(fileName); if(file.open(QIODevice::ReadOnly)) { QTextStream in(&file); while(!in.atEnd()) { QString line = in.readLine(); QStringList topics = line.split(" ", QString::SkipEmptyParts); QCOMPARE(client->checkCommonLevel(topics[0], topics[1]), topics[2]); } delete client; file.close(); } } void MQTTUnitTest::testCommonFalse(){ MQTTClient* client = new MQTTClient("test"); const QString fileName = m_dataDir + "common_false.txt"; QFile file(fileName); if(file.open(QIODevice::ReadOnly)) { QTextStream in(&file); while(!in.atEnd()) { QString line = in.readLine(); QStringList topics = line.split(" ", QString::SkipEmptyParts); QCOMPARE(client->checkCommonLevel(topics[0], topics[1]), ""); } delete client; file.close(); } } //############################################################################## //################# test handling of data received by messages ############### //############################################################################## void MQTTUnitTest::testIntegerMessage() { AsciiFilter* filter = new AsciiFilter(); filter->setAutoModeEnabled(true); Project* project = new Project(); MQTTClient* mqttClient = new MQTTClient("test"); project->addChild(mqttClient); mqttClient->setFilter(filter); mqttClient->setReadingType(MQTTClient::TillEnd); mqttClient->setKeepNValues(0); mqttClient->setUpdateType(MQTTClient::UpdateType::NewData); - mqttClient->setMqttClientHostPort("broker.hivemq.com", 1883); + mqttClient->setMQTTClientHostPort("broker.hivemq.com", 1883); mqttClient->setMQTTUseAuthentication(false); mqttClient->setMQTTUseID(false); QMqttTopicFilter topicFilter {"labplot/mqttUnitTest"}; - mqttClient->addInitialMqttSubscriptions(topicFilter, 0); + mqttClient->addInitialMQTTSubscriptions(topicFilter, 0); mqttClient->read(); mqttClient->ready(); QMqttClient* client = new QMqttClient(); client->setHostname("broker.hivemq.com"); client->setPort(1883); client->connectToHost(); QTest::qWaitFor([&]() { return (client->state() == QMqttClient::Connected); }, 5000); QMqttSubscription* subscription = client->subscribe(topicFilter, 0); if(subscription) { const QString fileName = m_dataDir + "integer_message_1.txt"; QFile file(fileName); if(file.open(QIODevice::ReadOnly)) { QTextStream in(&file); QString message = in.readAll(); client->publish(topicFilter.filter(), message.toUtf8(), 0); } file.close(); QTimer timer; timer.setSingleShot(true); QEventLoop* loop = new QEventLoop(); - connect(mqttClient, &MQTTClient::mqttTopicsChanged, loop, &QEventLoop::quit); + connect(mqttClient, &MQTTClient::MQTTTopicsChanged, loop, &QEventLoop::quit); connect( (&timer), &QTimer::timeout, loop, &QEventLoop::quit); timer.start(5000); loop->exec(); const MQTTTopic* testTopic = nullptr; if(timer.isActive()) { QVector topic = mqttClient->children (AbstractAspect::Recursive); for(int i = 0; i < topic.size(); ++i) { if (topic[i]->topicName() == "labplot/mqttUnitTest") { testTopic = topic[i]; break; } } Column* value = testTopic->column(testTopic->columnCount() - 1); QCOMPARE(value->columnMode(), Column::ColumnMode::Integer); QCOMPARE(value->rowCount(), 3); QCOMPARE(value->valueAt(0), 1); QCOMPARE(value->valueAt(1), 2); QCOMPARE(value->valueAt(2), 3); const QString fileName2 = m_dataDir + "integer_message_2.txt"; QFile file2(fileName2); if(file2.open(QIODevice::ReadOnly)) { QTextStream in2(&file2); QString message = in2.readAll(); client->publish(topicFilter.filter(), message.toUtf8(), 0); } file2.close(); QTest::qWait(1000); QCOMPARE(value->rowCount(), 8); QCOMPARE(value->valueAt(3), 6); QCOMPARE(value->valueAt(4), 0); QCOMPARE(value->valueAt(5), 0); QCOMPARE(value->valueAt(6), 0); QCOMPARE(value->valueAt(7), 3); } } } void MQTTUnitTest::testNumericMessage() { AsciiFilter* filter = new AsciiFilter(); filter->setAutoModeEnabled(true); Project* project = new Project(); MQTTClient* mqttClient = new MQTTClient("test"); project->addChild(mqttClient); mqttClient->setFilter(filter); mqttClient->setReadingType(MQTTClient::TillEnd); mqttClient->setKeepNValues(0); mqttClient->setUpdateType(MQTTClient::UpdateType::NewData); - mqttClient->setMqttClientHostPort("broker.hivemq.com", 1883); + mqttClient->setMQTTClientHostPort("broker.hivemq.com", 1883); mqttClient->setMQTTUseAuthentication(false); mqttClient->setMQTTUseID(false); QMqttTopicFilter topicFilter {"labplot/mqttUnitTest"}; - mqttClient->addInitialMqttSubscriptions(topicFilter, 0); + mqttClient->addInitialMQTTSubscriptions(topicFilter, 0); mqttClient->read(); mqttClient->ready(); QMqttClient* client = new QMqttClient(); client->setHostname("broker.hivemq.com"); client->setPort(1883); client->connectToHost(); QTest::qWaitFor([&]() { return (client->state() == QMqttClient::Connected); }, 5000); QMqttSubscription* subscription = client->subscribe(topicFilter, 0); if(subscription) { const QString fileName = m_dataDir + "numeric_message_1.txt"; QFile file(fileName); if(file.open(QIODevice::ReadOnly)) { QTextStream in(&file); QString message = in.readAll(); client->publish(topicFilter.filter(), message.toUtf8(), 0); } file.close(); QTimer timer; timer.setSingleShot(true); QEventLoop* loop = new QEventLoop(); - connect(mqttClient, &MQTTClient::mqttTopicsChanged, loop, &QEventLoop::quit); + connect(mqttClient, &MQTTClient::MQTTTopicsChanged, loop, &QEventLoop::quit); connect( (&timer), &QTimer::timeout, loop, &QEventLoop::quit); timer.start(5000); loop->exec(); const MQTTTopic* testTopic = nullptr; if(timer.isActive()) { QVector topic = mqttClient->children (AbstractAspect::Recursive); for(int i = 0; i < topic.size(); ++i) { if (topic[i]->topicName() == "labplot/mqttUnitTest") { testTopic = topic[i]; break; } } Column* value = testTopic->column(testTopic->columnCount() - 1); QCOMPARE(value->columnMode(), Column::ColumnMode::Numeric); QCOMPARE(value->rowCount(), 3); QCOMPARE(value->valueAt(0), 1.5); QCOMPARE(value->valueAt(1), 2.7); QCOMPARE(value->valueAt(2), 3.9); const QString fileName2 = m_dataDir + "numeric_message_2.txt"; QFile file2(fileName2); if(file2.open(QIODevice::ReadOnly)) { QTextStream in2(&file2); QString message = in2.readAll(); client->publish(topicFilter.filter(), message.toUtf8(), 0); } file2.close(); QTest::qWait(1000); QCOMPARE(value->rowCount(), 8); QCOMPARE(value->valueAt(3), 6); QCOMPARE((bool)std::isnan(value->valueAt(4)), true); QCOMPARE((bool)std::isnan(value->valueAt(5)), true); QCOMPARE((bool)std::isnan(value->valueAt(6)), true); QCOMPARE(value->valueAt(7), 0.0098); } } } void MQTTUnitTest::testTextMessage() { AsciiFilter* filter = new AsciiFilter(); filter->setAutoModeEnabled(true); Project* project = new Project(); MQTTClient* mqttClient = new MQTTClient("test"); project->addChild(mqttClient); mqttClient->setFilter(filter); mqttClient->setReadingType(MQTTClient::TillEnd); mqttClient->setKeepNValues(0); mqttClient->setUpdateType(MQTTClient::UpdateType::NewData); - mqttClient->setMqttClientHostPort("broker.hivemq.com", 1883); + mqttClient->setMQTTClientHostPort("broker.hivemq.com", 1883); mqttClient->setMQTTUseAuthentication(false); mqttClient->setMQTTUseID(false); QMqttTopicFilter topicFilter {"labplot/mqttUnitTest"}; - mqttClient->addInitialMqttSubscriptions(topicFilter, 0); + mqttClient->addInitialMQTTSubscriptions(topicFilter, 0); mqttClient->read(); mqttClient->ready(); QMqttClient* client = new QMqttClient(); client->setHostname("broker.hivemq.com"); client->setPort(1883); client->connectToHost(); QTest::qWaitFor([&]() { return (client->state() == QMqttClient::Connected); }, 5000); QMqttSubscription* subscription = client->subscribe(topicFilter, 0); if(subscription) { const QString fileName = m_dataDir + "text_message.txt"; QFile file(fileName); if(file.open(QIODevice::ReadOnly)) { QTextStream in(&file); QString message = in.readAll(); client->publish(topicFilter.filter(), message.toUtf8(), 0); } file.close(); QTimer timer; timer.setSingleShot(true); QEventLoop* loop = new QEventLoop(); - connect(mqttClient, &MQTTClient::mqttTopicsChanged, loop, &QEventLoop::quit); + connect(mqttClient, &MQTTClient::MQTTTopicsChanged, loop, &QEventLoop::quit); connect( (&timer), &QTimer::timeout, loop, &QEventLoop::quit); timer.start(5000); loop->exec(); const MQTTTopic* testTopic = nullptr; if(timer.isActive()) { QVector topic = mqttClient->children (AbstractAspect::Recursive); for(int i = 0; i < topic.size(); ++i) { if (topic[i]->topicName() == "labplot/mqttUnitTest") { testTopic = topic[i]; break; } } Column* value = testTopic->column(testTopic->columnCount() - 1); QCOMPARE(value->columnMode(), Column::ColumnMode::Text); QCOMPARE(value->rowCount(), 5); QCOMPARE(value->textAt(0), "ball"); QCOMPARE(value->textAt(1), "cat"); QCOMPARE(value->textAt(2), "dog"); QCOMPARE(value->textAt(3), "house"); QCOMPARE(value->textAt(4), "Barcelona"); } } } //############################################################################## //##################### test subscribing and unsubscribing ################### //############################################################################## void MQTTUnitTest::testSubscriptions() { AsciiFilter* filter = new AsciiFilter(); filter->setAutoModeEnabled(true); Project* project = new Project(); MQTTClient* mqttClient = new MQTTClient("test"); project->addChild(mqttClient); mqttClient->setFilter(filter); mqttClient->setReadingType(MQTTClient::TillEnd); mqttClient->setKeepNValues(0); mqttClient->setUpdateType(MQTTClient::UpdateType::NewData); - mqttClient->setMqttClientHostPort("broker.hivemq.com", 1883); + mqttClient->setMQTTClientHostPort("broker.hivemq.com", 1883); mqttClient->setMQTTUseAuthentication(false); mqttClient->setMQTTUseID(false); - mqttClient->setMqttWillUse(false); + mqttClient->setMQTTWillUse(false); QMqttTopicFilter topicFilter {"labplot/mqttUnitTest"}; - mqttClient->addInitialMqttSubscriptions(topicFilter, 0); + mqttClient->addInitialMQTTSubscriptions(topicFilter, 0); LiveDataDock* liveDock = new LiveDataDock(); QList list; list.push_back(mqttClient); liveDock->setMQTTClients(list); mqttClient->read(); mqttClient->ready(); QTimer timer; timer.setSingleShot(true); QEventLoop* loop = new QEventLoop(); - connect(mqttClient, &MQTTClient::mqttSubscribed, loop, &QEventLoop::quit); + connect(mqttClient, &MQTTClient::MQTTSubscribed, loop, &QEventLoop::quit); connect( (&timer), &QTimer::timeout, loop, &QEventLoop::quit); timer.start(5000); loop->exec(); if(timer.isActive()) { delete loop; QMqttClient* client = new QMqttClient(); client->setHostname("broker.hivemq.com"); client->setPort(1883); client->connectToHost(); QTest::qWaitFor([&]() { return (client->state() == QMqttClient::Connected); }, 3000); QString fileName = m_dataDir + "subscribe_1.txt"; QFile* file = new QFile(fileName); QTest::qWait(1000); if(file->open(QIODevice::ReadOnly)) { QTextStream in(file); while(!in.atEnd()) { QString line = in.readLine(); QMqttTopicFilter filter{line}; client->publish(filter.filter(), QString("test").toUtf8()); QTimer timer2; timer2.setSingleShot(true); loop = new QEventLoop(); connect( (&timer2), &QTimer::timeout, loop, &QEventLoop::quit); connect(liveDock, &LiveDataDock::newTopic, this, [line, loop](const QString& topic) { if(topic == line) { loop->quit(); } }); timer2.start(5000); loop->exec(); disconnect(liveDock, &LiveDataDock::newTopic, this, 0); } } liveDock->testUnsubscribe("labplot/mqttUnitTest"); file->close(); delete file; fileName = m_dataDir + "subscribe_2.txt"; file = new QFile(fileName); if(file->open(QIODevice::ReadOnly)) { QTextStream in(file); while(!in.atEnd()) { QString topic = in.readLine(); bool found = liveDock->testSubscribe(topic); QCOMPARE(found, true); } } file->close(); delete file; fileName = m_dataDir + "subscribe_2_result.txt"; file = new QFile(fileName); if(file->open(QIODevice::ReadOnly)) { QTextStream in(file); int count = in.readLine().simplified().toInt(); - QCOMPARE(mqttClient->mqttSubscriptions().size(), count); + QCOMPARE(mqttClient->MQTTSubscriptions().size(), count); while(!in.atEnd()) { QString topic = in.readLine(); - QVector subscriptions = mqttClient->mqttSubscriptions(); + QVector subscriptions = mqttClient->MQTTSubscriptions(); QCOMPARE(subscriptions.contains(topic), true); } } file->close(); delete file; fileName = m_dataDir + "unsubscribe_1.txt"; file = new QFile(fileName); if(file->open(QIODevice::ReadOnly)) { QTextStream in(file); while(!in.atEnd()) { QString topic = in.readLine(); bool found = liveDock->testUnsubscribe(topic); QCOMPARE(found, true); } } file->close(); delete file; fileName = m_dataDir + "unsubscribe_1_result.txt"; file = new QFile(fileName); if(file->open(QIODevice::ReadOnly)) { QTextStream in(file); int count = in.readLine().simplified().toInt(); - QCOMPARE(mqttClient->mqttSubscriptions().size(), count); + QCOMPARE(mqttClient->MQTTSubscriptions().size(), count); while(!in.atEnd()) { QString topic = in.readLine(); - QVector subscriptions = mqttClient->mqttSubscriptions(); + QVector subscriptions = mqttClient->MQTTSubscriptions(); QCOMPARE(subscriptions.contains(topic), true); } } file->close(); delete file; fileName = m_dataDir + "subscribe_3.txt"; file = new QFile(fileName); if(file->open(QIODevice::ReadOnly)) { QTextStream in(file); while(!in.atEnd()) { QString topic = in.readLine(); - QVector subscriptions = mqttClient->mqttSubscriptions(); + QVector subscriptions = mqttClient->MQTTSubscriptions(); bool found = liveDock->testSubscribe(topic); QCOMPARE(found, true); } } file->close(); delete file; fileName = m_dataDir + "subscribe_3_result.txt"; file = new QFile(fileName); if(file->open(QIODevice::ReadOnly)) { QTextStream in(file); int count = in.readLine().simplified().toInt(); - QVector sub = mqttClient->mqttSubscriptions(); - QCOMPARE(mqttClient->mqttSubscriptions().size(), count); + QVector sub = mqttClient->MQTTSubscriptions(); + QCOMPARE(mqttClient->MQTTSubscriptions().size(), count); while(!in.atEnd()) { QString topic = in.readLine(); - QVector subscriptions = mqttClient->mqttSubscriptions(); + QVector subscriptions = mqttClient->MQTTSubscriptions(); QCOMPARE(subscriptions.contains(topic), true); } } file->close(); delete file; fileName = m_dataDir + "unsubscribe_2.txt"; file = new QFile(fileName); if(file->open(QIODevice::ReadOnly)) { QTextStream in(file); while(!in.atEnd()) { QString topic = in.readLine(); bool found = liveDock->testUnsubscribe(topic); QCOMPARE(found, true); } } file->close(); delete file; fileName = m_dataDir + "unsubscribe_2_result.txt"; file = new QFile(fileName); if(file->open(QIODevice::ReadOnly)) { QTextStream in(file); int count = in.readLine().simplified().toInt(); - QCOMPARE(mqttClient->mqttSubscriptions().size(), count); + QCOMPARE(mqttClient->MQTTSubscriptions().size(), count); - QVector subscriptions = mqttClient->mqttSubscriptions(); + QVector subscriptions = mqttClient->MQTTSubscriptions(); while(!in.atEnd()) { QString topic = in.readLine(); QCOMPARE(subscriptions.contains(topic), true); } } file->close(); delete file; } } QTEST_MAIN(MQTTUnitTest) #endif //HAVE_MQTT