File indexing completed on 2024-05-12 03:47:43
0001 /* 0002 File : MQTTTopic.cpp 0003 Project : LabPlot 0004 Description : Represents a topic of a MQTTSubscription 0005 -------------------------------------------------------------------- 0006 SPDX-FileCopyrightText: 2018 Kovacs Ferencz <kferike98@gmail.com> 0007 0008 SPDX-License-Identifier: GPL-2.0-or-later 0009 */ 0010 #include "backend/datasources/MQTTTopic.h" 0011 0012 #include "backend/datasources/MQTTClient.h" 0013 #include "backend/datasources/MQTTSubscription.h" 0014 #include "backend/datasources/filters/AsciiFilter.h" 0015 #include "backend/lib/XmlStreamReader.h" 0016 #include "commonfrontend/spreadsheet/SpreadsheetView.h" 0017 #include "kdefrontend/spreadsheet/PlotDataDialog.h" 0018 0019 #include <KLocalizedString> 0020 #include <QAction> 0021 #include <QIcon> 0022 #include <QMenu> 0023 0024 /*! 0025 \class MQTTTopic 0026 \brief Represents a topic of a subscription made in MQTTClient. 0027 0028 \ingroup datasources 0029 */ 0030 MQTTTopic::MQTTTopic(const QString& name, MQTTSubscription* subscription, bool loading) 0031 : Spreadsheet(name, loading, AspectType::MQTTTopic) 0032 , m_topicName(name) 0033 , m_MQTTClient(subscription->mqttClient()) 0034 , m_filter(new AsciiFilter) { 0035 auto mainFilter = m_MQTTClient->filter(); 0036 0037 m_filter->setAutoModeEnabled(mainFilter->isAutoModeEnabled()); 0038 if (!mainFilter->isAutoModeEnabled()) { 0039 m_filter->setCommentCharacter(mainFilter->commentCharacter()); 0040 m_filter->setSeparatingCharacter(mainFilter->separatingCharacter()); 0041 m_filter->setDateTimeFormat(mainFilter->dateTimeFormat()); 0042 m_filter->setCreateIndexEnabled(mainFilter->createIndexEnabled()); 0043 m_filter->setCreateTimestampEnabled(mainFilter->createTimestampEnabled()); 0044 m_filter->setSimplifyWhitespacesEnabled(mainFilter->simplifyWhitespacesEnabled()); 0045 m_filter->setNaNValueToZero(mainFilter->NaNValueToZeroEnabled()); 0046 m_filter->setRemoveQuotesEnabled(mainFilter->removeQuotesEnabled()); 0047 m_filter->setSkipEmptyParts(mainFilter->skipEmptyParts()); 0048 m_filter->setHeaderEnabled(mainFilter->isHeaderEnabled()); 0049 QString vectorNames; 0050 const QStringList& filterVectorNames = mainFilter->vectorNames(); 0051 for (int i = 0; i < filterVectorNames.size(); ++i) { 0052 vectorNames.append(filterVectorNames.at(i)); 0053 if (i != vectorNames.size() - 1) 0054 vectorNames.append(QLatin1String(" ")); 0055 } 0056 0057 m_filter->setVectorNames(vectorNames); 0058 m_filter->setStartRow(mainFilter->startRow()); 0059 m_filter->setEndRow(mainFilter->endRow()); 0060 m_filter->setStartColumn(mainFilter->startColumn()); 0061 m_filter->setEndColumn(mainFilter->endColumn()); 0062 } 0063 0064 connect(m_MQTTClient, &MQTTClient::readFromTopics, this, &MQTTTopic::read); 0065 qDebug() << "New MqttTopic: " << m_topicName; 0066 initActions(); 0067 } 0068 0069 MQTTTopic::~MQTTTopic() { 0070 qDebug() << "MqttTopic destructor:" << m_topicName; 0071 delete m_filter; 0072 } 0073 0074 /*! 0075 *\brief Sets the MQTTTopic's filter 0076 * The ownership of the filter is passed to MQTTTopic. 0077 * 0078 * \param filter 0079 */ 0080 void MQTTTopic::setFilter(AsciiFilter* f) { 0081 delete m_filter; 0082 m_filter = f; 0083 } 0084 0085 /*! 0086 *\brief Returns the MQTTTopic's filter 0087 */ 0088 AsciiFilter* MQTTTopic::filter() const { 0089 return m_filter; 0090 } 0091 0092 /*! 0093 *\brief Returns the MQTTTopic's icon 0094 */ 0095 QIcon MQTTTopic::icon() const { 0096 return QIcon::fromTheme(QStringLiteral("text-plain")); 0097 } 0098 0099 /*! 0100 *\brief Adds an action to the MQTTTopic's context menu in the project explorer 0101 */ 0102 QMenu* MQTTTopic::createContextMenu() { 0103 QMenu* menu = AbstractPart::createContextMenu(); 0104 0105 QAction* firstAction = nullptr; 0106 // if we're populating the context menu for the project explorer, then 0107 // there're already actions available there. Skip the first title-action 0108 // and insert the action at the beginning of the menu. 0109 if (menu->actions().size() > 1) 0110 firstAction = menu->actions().at(1); 0111 0112 menu->insertAction(firstAction, m_plotDataAction); 0113 menu->insertSeparator(firstAction); 0114 0115 return menu; 0116 } 0117 0118 QWidget* MQTTTopic::view() const { 0119 if (!m_partView) 0120 m_partView = new SpreadsheetView(const_cast<MQTTTopic*>(this), true); 0121 return m_partView; 0122 } 0123 0124 /*! 0125 *\brief Adds a message received by the topic to the message puffer 0126 */ 0127 void MQTTTopic::newMessage(const QString& message) { 0128 m_messagePuffer.push_back(message); 0129 } 0130 0131 /*! 0132 *\brief Returns the name of the MQTTTopic 0133 */ 0134 QString MQTTTopic::topicName() const { 0135 return m_topicName; 0136 } 0137 0138 /*! 0139 *\brief Initializes the actions of MQTTTopic 0140 */ 0141 void MQTTTopic::initActions() { 0142 m_plotDataAction = new QAction(QIcon::fromTheme(QStringLiteral("office-chart-line")), i18n("Plot data"), this); 0143 connect(m_plotDataAction, &QAction::triggered, this, &MQTTTopic::plotData); 0144 } 0145 0146 /*! 0147 *\brief Returns the MQTTClient the topic belongs to 0148 */ 0149 MQTTClient* MQTTTopic::mqttClient() const { 0150 return m_MQTTClient; 0151 } 0152 0153 // ############################################################################## 0154 // ################################# SLOTS #################################### 0155 // ############################################################################## 0156 0157 /*! 0158 *\brief Plots the data stored in MQTTTopic 0159 */ 0160 void MQTTTopic::plotData() { 0161 auto* dlg = new PlotDataDialog(this); 0162 dlg->exec(); 0163 } 0164 0165 /*! 0166 *\brief Reads every message from the message puffer 0167 */ 0168 void MQTTTopic::read() { 0169 while (!m_messagePuffer.isEmpty()) { 0170 qDebug() << "Reading from topic " << m_topicName; 0171 const QString tempMessage = m_messagePuffer.takeFirst(); 0172 m_filter->readMQTTTopic(tempMessage, this); 0173 } 0174 } 0175 0176 // ############################################################################## 0177 // ################## Serialization/Deserialization ########################### 0178 // ############################################################################## 0179 /*! 0180 Saves as XML. 0181 */ 0182 void MQTTTopic::save(QXmlStreamWriter* writer) const { 0183 writer->writeStartElement(QStringLiteral("MQTTTopic")); 0184 writeBasicAttributes(writer); 0185 writeCommentElement(writer); 0186 0187 // general 0188 writer->writeStartElement(QStringLiteral("general")); 0189 writer->writeAttribute(QStringLiteral("topicName"), m_topicName); 0190 writer->writeAttribute(QStringLiteral("filterPrepared"), QString::number(m_filter->isPrepared())); 0191 writer->writeAttribute(QStringLiteral("filterSeparator"), m_filter->separator()); 0192 writer->writeAttribute(QStringLiteral("messagePufferSize"), QString::number(m_messagePuffer.size())); 0193 for (int i = 0; i < m_messagePuffer.count(); ++i) 0194 writer->writeAttribute(QStringLiteral("message") + QString::number(i), m_messagePuffer[i]); 0195 writer->writeEndElement(); 0196 0197 // filter 0198 m_filter->save(writer); 0199 0200 // Columns 0201 for (auto* col : children<Column>(AbstractAspect::ChildIndexFlag::IncludeHidden)) 0202 col->save(writer); 0203 0204 writer->writeEndElement(); // MQTTTopic 0205 } 0206 0207 /*! 0208 Loads from XML. 0209 */ 0210 bool MQTTTopic::load(XmlStreamReader* reader, bool preview) { 0211 removeColumns(0, columnCount()); 0212 if (!readBasicAttributes(reader)) 0213 return false; 0214 0215 bool isFilterPrepared = false; 0216 QString separator; 0217 0218 QString attributeWarning = i18n("Attribute '%1' missing or empty, default value is used"); 0219 QXmlStreamAttributes attribs; 0220 QString str; 0221 0222 while (!reader->atEnd()) { 0223 reader->readNext(); 0224 if (reader->isEndElement() && reader->name() == QLatin1String("MQTTTopic")) 0225 break; 0226 0227 if (!reader->isStartElement()) 0228 continue; 0229 0230 if (reader->name() == QLatin1String("comment")) { 0231 if (!readCommentElement(reader)) 0232 return false; 0233 } else if (reader->name() == QLatin1String("general")) { 0234 attribs = reader->attributes(); 0235 0236 str = attribs.value(QStringLiteral("topicName")).toString(); 0237 if (str.isEmpty()) 0238 reader->raiseWarning(attributeWarning.arg(QStringLiteral("'topicName'"))); 0239 else { 0240 m_topicName = str; 0241 setName(str); 0242 } 0243 0244 str = attribs.value(QStringLiteral("filterPrepared")).toString(); 0245 if (str.isEmpty()) 0246 reader->raiseWarning(attributeWarning.arg(QStringLiteral("'filterPrepared'"))); 0247 else { 0248 isFilterPrepared = str.toInt(); 0249 } 0250 0251 str = attribs.value(QStringLiteral("filterSeparator")).toString(); 0252 if (str.isEmpty()) 0253 reader->raiseWarning(attributeWarning.arg(QStringLiteral("'filterSeparator'"))); 0254 else { 0255 separator = str; 0256 } 0257 0258 int pufferSize = 0; 0259 str = attribs.value(QStringLiteral("messagePufferSize")).toString(); 0260 if (str.isEmpty()) 0261 reader->raiseWarning(attributeWarning.arg(QStringLiteral("'messagePufferSize'"))); 0262 else 0263 pufferSize = str.toInt(); 0264 for (int i = 0; i < pufferSize; ++i) { 0265 str = attribs.value(QStringLiteral("message") + QString::number(i)).toString(); 0266 if (str.isEmpty()) 0267 reader->raiseWarning(attributeWarning.arg(QStringLiteral("'message") + QString::number(i) + QLatin1Char('\''))); 0268 else 0269 m_messagePuffer.push_back(str); 0270 } 0271 } else if (reader->name() == QLatin1String("asciiFilter")) { 0272 if (!m_filter->load(reader)) 0273 return false; 0274 } else if (reader->name() == QLatin1String("column")) { 0275 Column* column = new Column(QString(), AbstractColumn::ColumnMode::Text); 0276 if (!column->load(reader, preview)) { 0277 delete column; 0278 setColumnCount(0); 0279 return false; 0280 } 0281 addChild(column); 0282 } else { // unknown element 0283 reader->raiseWarning(i18n("unknown element '%1'", reader->name().toString())); 0284 if (!reader->skipToEndElement()) 0285 return false; 0286 } 0287 } 0288 0289 // prepare filter for reading 0290 m_filter->setPreparedForMQTT(isFilterPrepared, this, separator); 0291 0292 return !reader->hasError(); 0293 }