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