File indexing completed on 2024-05-19 15:01:36
0001 /*************************************************************************** 0002 File : LiveDataSource.cpp 0003 Project : LabPlot 0004 Description : Represents live data source 0005 -------------------------------------------------------------------- 0006 Copyright : (C) 2009-2019 Alexander Semke (alexander.semke@web.de) 0007 Copyright : (C) 2017 Fabian Kristof (fkristofszabolcs@gmail.com) 0008 Copyright : (C) 2018 Stefan Gerlach (stefan.gerlach@uni.kn) 0009 0010 ***************************************************************************/ 0011 0012 /*************************************************************************** 0013 * * 0014 * This program is free software; you can redistribute it and/or modify * 0015 * it under the terms of the GNU General Public License as published by * 0016 * the Free Software Foundation; either version 2 of the License, or * 0017 * (at your option) any later version. * 0018 * * 0019 * This program is distributed in the hope that it will be useful, * 0020 * but WITHOUT ANY WARRANTY; without even the implied warranty of * 0021 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 0022 * GNU General Public License for more details. * 0023 * * 0024 * You should have received a copy of the GNU General Public License * 0025 * along with this program; if not, write to the Free Software * 0026 * Foundation, Inc., 51 Franklin Street, Fifth Floor, * 0027 * Boston, MA 02110-1301 USA * 0028 * * 0029 ***************************************************************************/ 0030 0031 #include "backend/datasources/LiveDataSource.h" 0032 #include "backend/datasources/filters/AsciiFilter.h" 0033 #include "backend/datasources/filters/FITSFilter.h" 0034 #include "backend/datasources/filters/BinaryFilter.h" 0035 #include "backend/datasources/filters/ROOTFilter.h" 0036 #include "backend/core/Project.h" 0037 #include "commonfrontend/spreadsheet/SpreadsheetView.h" 0038 #include "kdefrontend/spreadsheet/PlotDataDialog.h" 0039 0040 #include <QAction> 0041 #include <QDateTime> 0042 #include <QDir> 0043 #include <QFileInfo> 0044 #include <QFileSystemWatcher> 0045 #include <QIcon> 0046 #include <QMenu> 0047 #include <QMessageBox> 0048 #include <QProcess> 0049 #include <QTimer> 0050 #include <QTcpSocket> 0051 #include <QUdpSocket> 0052 #ifdef HAVE_QTSERIALPORT 0053 #include <QSerialPortInfo> 0054 #endif 0055 0056 #include <KLocalizedString> 0057 0058 /*! 0059 \class LiveDataSource 0060 \brief Represents data stored in a file. Reading and writing is done with the help of appropriate I/O-filters. 0061 0062 \ingroup datasources 0063 */ 0064 LiveDataSource::LiveDataSource(const QString& name, bool loading) : Spreadsheet(name, loading, AspectType::LiveDataSource), 0065 m_updateTimer(new QTimer(this)), m_watchTimer(new QTimer(this)) { 0066 0067 initActions(); 0068 0069 connect(m_updateTimer, &QTimer::timeout, this, &LiveDataSource::read); 0070 connect(m_watchTimer, &QTimer::timeout, this, &LiveDataSource::readOnUpdate); 0071 } 0072 0073 LiveDataSource::~LiveDataSource() { 0074 //stop reading before deleting the objects 0075 pauseReading(); 0076 0077 delete m_filter; 0078 delete m_fileSystemWatcher; 0079 delete m_localSocket; 0080 delete m_tcpSocket; 0081 #ifdef HAVE_QTSERIALPORT 0082 delete m_serialPort; 0083 #endif 0084 } 0085 0086 void LiveDataSource::initActions() { 0087 m_plotDataAction = new QAction(QIcon::fromTheme("office-chart-line"), i18n("Plot data"), this); 0088 connect(m_plotDataAction, &QAction::triggered, this, &LiveDataSource::plotData); 0089 m_watchTimer->setSingleShot(true); 0090 m_watchTimer->setInterval(100); 0091 } 0092 0093 QWidget* LiveDataSource::view() const { 0094 if (!m_partView) { 0095 m_view = new SpreadsheetView(const_cast<LiveDataSource*>(this), true); 0096 m_partView = m_view; 0097 } 0098 return m_partView; 0099 } 0100 0101 /*! 0102 * \brief Returns a list with the names of the available ports 0103 */ 0104 QStringList LiveDataSource::availablePorts() { 0105 QStringList ports; 0106 // qDebug() << "available ports count:" << QSerialPortInfo::availablePorts().size(); 0107 0108 #ifdef HAVE_QTSERIALPORT 0109 for (const QSerialPortInfo& sp : QSerialPortInfo::availablePorts()) { 0110 ports.append(sp.portName()); 0111 0112 DEBUG(" port " << STDSTRING(sp.portName()) << ": " << STDSTRING(sp.systemLocation()) << STDSTRING(sp.description()) 0113 << ' ' << STDSTRING(sp.manufacturer()) << ' ' << STDSTRING(sp.serialNumber())); 0114 } 0115 // For Testing: 0116 // ports.append("/dev/pts/26"); 0117 #endif 0118 0119 return ports; 0120 } 0121 0122 /*! 0123 * \brief Returns a list with the supported baud rates 0124 */ 0125 QStringList LiveDataSource::supportedBaudRates() { 0126 QStringList baudRates; 0127 0128 #ifdef HAVE_QTSERIALPORT 0129 for (const auto& baud : QSerialPortInfo::standardBaudRates()) 0130 baudRates.append(QString::number(baud)); 0131 #endif 0132 return baudRates; 0133 } 0134 0135 /*! 0136 * \brief Updates this data source at this moment 0137 */ 0138 void LiveDataSource::updateNow() { 0139 DEBUG("LiveDataSource::updateNow() update interval = " << m_updateInterval); 0140 if (m_updateType == UpdateType::TimeInterval) 0141 m_updateTimer->stop(); 0142 else 0143 m_pending = false; 0144 read(); 0145 0146 //restart the timer after update 0147 if (m_updateType == UpdateType::TimeInterval && !m_paused) 0148 m_updateTimer->start(m_updateInterval); 0149 } 0150 0151 /*! 0152 * \brief Continue reading from the live data source after it was paused 0153 */ 0154 void LiveDataSource::continueReading() { 0155 m_paused = false; 0156 if (m_pending) { 0157 m_pending = false; 0158 updateNow(); 0159 } 0160 } 0161 0162 /*! 0163 * \brief Pause the reading of the live data source 0164 */ 0165 void LiveDataSource::pauseReading() { 0166 m_paused = true; 0167 if (m_updateType == UpdateType::TimeInterval) { 0168 m_pending = true; 0169 m_updateTimer->stop(); 0170 } 0171 } 0172 0173 void LiveDataSource::setFileName(const QString& name) { 0174 m_fileName = name; 0175 } 0176 0177 QString LiveDataSource::fileName() const { 0178 return m_fileName; 0179 } 0180 0181 /*! 0182 * \brief Sets the local socket's server name to name 0183 * \param name 0184 */ 0185 void LiveDataSource::setLocalSocketName(const QString& name) { 0186 m_localSocketName = name; 0187 } 0188 0189 QString LiveDataSource::localSocketName() const { 0190 return m_localSocketName; 0191 } 0192 0193 void LiveDataSource::setFileType(AbstractFileFilter::FileType type) { 0194 m_fileType = type; 0195 } 0196 0197 AbstractFileFilter::FileType LiveDataSource::fileType() const { 0198 return m_fileType; 0199 } 0200 0201 void LiveDataSource::setFilter(AbstractFileFilter* f) { 0202 delete m_filter; 0203 m_filter = f; 0204 } 0205 0206 AbstractFileFilter* LiveDataSource::filter() const { 0207 return m_filter; 0208 } 0209 0210 /*! 0211 * \brief Sets the serial port's baud rate 0212 * \param baudrate 0213 */ 0214 void LiveDataSource::setBaudRate(int baudrate) { 0215 m_baudRate = baudrate; 0216 } 0217 0218 int LiveDataSource::baudRate() const { 0219 return m_baudRate; 0220 } 0221 0222 /*! 0223 * \brief Sets the source's update interval to \c interval 0224 * \param interval 0225 */ 0226 void LiveDataSource::setUpdateInterval(int interval) { 0227 m_updateInterval = interval; 0228 if (!m_paused) 0229 m_updateTimer->start(m_updateInterval); 0230 } 0231 0232 int LiveDataSource::updateInterval() const { 0233 return m_updateInterval; 0234 } 0235 0236 /*! 0237 * \brief Sets how many values we should keep when keepLastValues is true 0238 * \param keepnvalues 0239 */ 0240 void LiveDataSource::setKeepNValues(int keepnvalues) { 0241 m_keepNValues = keepnvalues; 0242 } 0243 0244 int LiveDataSource::keepNValues() const { 0245 return m_keepNValues; 0246 } 0247 0248 /*! 0249 * \brief Sets the network socket's port to port 0250 * \param port 0251 */ 0252 void LiveDataSource::setPort(quint16 port) { 0253 m_port = port; 0254 } 0255 0256 void LiveDataSource::setBytesRead(qint64 bytes) { 0257 m_bytesRead = bytes; 0258 } 0259 0260 int LiveDataSource::bytesRead() const { 0261 return m_bytesRead; 0262 } 0263 0264 int LiveDataSource::port() const { 0265 return m_port; 0266 } 0267 0268 /*! 0269 * \brief Sets the serial port's name to name 0270 * \param name 0271 */ 0272 void LiveDataSource::setSerialPort(const QString& name) { 0273 m_serialPortName = name; 0274 } 0275 0276 QString LiveDataSource::serialPortName() const { 0277 return m_serialPortName; 0278 } 0279 0280 bool LiveDataSource::isPaused() const { 0281 return m_paused; 0282 } 0283 0284 /*! 0285 * \brief Sets the sample size to size 0286 * \param size 0287 */ 0288 void LiveDataSource::setSampleSize(int size) { 0289 m_sampleSize = size; 0290 } 0291 0292 int LiveDataSource::sampleSize() const { 0293 return m_sampleSize; 0294 } 0295 0296 /*! 0297 * \brief Sets the source's type to sourcetype 0298 * \param sourcetype 0299 */ 0300 void LiveDataSource::setSourceType(SourceType sourcetype) { 0301 m_sourceType = sourcetype; 0302 } 0303 0304 LiveDataSource::SourceType LiveDataSource::sourceType() const { 0305 return m_sourceType; 0306 } 0307 0308 /*! 0309 * \brief Sets the source's reading type to readingType 0310 * \param readingType 0311 */ 0312 void LiveDataSource::setReadingType(ReadingType readingType) { 0313 m_readingType = readingType; 0314 } 0315 0316 LiveDataSource::ReadingType LiveDataSource::readingType() const { 0317 return m_readingType; 0318 } 0319 0320 /*! 0321 * \brief Sets the source's update type to updatetype and handles this change 0322 * \param updatetype 0323 */ 0324 void LiveDataSource::setUpdateType(UpdateType updatetype) { 0325 switch (updatetype) { 0326 case UpdateType::NewData: { 0327 m_updateTimer->stop(); 0328 if (!m_fileSystemWatcher) 0329 m_fileSystemWatcher = new QFileSystemWatcher(this); 0330 0331 m_fileSystemWatcher->addPath(m_fileName); 0332 QFileInfo file(m_fileName); 0333 // If the watched file currently does not exist (because it is recreated for instance), watch its containing 0334 // directory instead. Once the file exists again, switch to watching the file in readOnUpdate(). 0335 // Reading will only start 100ms after the last update, to prevent continuous re-reading while the file is updated. 0336 // If the watched file intentionally is updated more often than that, the user should switch to periodic reading. 0337 if (m_fileSystemWatcher->files().contains(m_fileName)) 0338 m_fileSystemWatcher->removePath(file.absolutePath()); 0339 else 0340 m_fileSystemWatcher->addPath(file.absolutePath()); 0341 0342 connect(m_fileSystemWatcher, &QFileSystemWatcher::fileChanged, this, [&](){ m_watchTimer->start(); }); 0343 connect(m_fileSystemWatcher, &QFileSystemWatcher::directoryChanged, this, [&](){ m_watchTimer->start(); }); 0344 break; 0345 } 0346 case UpdateType::TimeInterval: 0347 delete m_fileSystemWatcher; 0348 m_fileSystemWatcher = nullptr; 0349 break; 0350 } 0351 m_updateType = updatetype; 0352 } 0353 0354 LiveDataSource::UpdateType LiveDataSource::updateType() const { 0355 return m_updateType; 0356 } 0357 0358 /*! 0359 * \brief Sets the network socket's host 0360 * \param host 0361 */ 0362 void LiveDataSource::setHost(const QString& host) { 0363 m_host = host.simplified(); 0364 } 0365 0366 QString LiveDataSource::host() const { 0367 return m_host; 0368 } 0369 0370 /*! 0371 sets whether only a link to the file is saved in the project file (\c b=true) 0372 or the whole content of the file (\c b=false). 0373 */ 0374 void LiveDataSource::setFileLinked(bool b) { 0375 m_fileLinked = b; 0376 } 0377 0378 /*! 0379 returns \c true if only a link to the file is saved in the project file. 0380 \c false otherwise. 0381 */ 0382 bool LiveDataSource::isFileLinked() const { 0383 return m_fileLinked; 0384 } 0385 0386 void LiveDataSource::setUseRelativePath(bool b) { 0387 m_relativePath = b; 0388 } 0389 0390 bool LiveDataSource::useRelativePath() const { 0391 return m_relativePath; 0392 } 0393 0394 QIcon LiveDataSource::icon() const { 0395 QIcon icon; 0396 0397 switch (m_fileType) { 0398 case AbstractFileFilter::FileType::Ascii: 0399 icon = QIcon::fromTheme("text-plain"); 0400 break; 0401 case AbstractFileFilter::FileType::Binary: 0402 icon = QIcon::fromTheme("application-octet-stream"); 0403 break; 0404 case AbstractFileFilter::FileType::Image: 0405 icon = QIcon::fromTheme("image-x-generic"); 0406 break; 0407 // TODO: missing icons 0408 case AbstractFileFilter::FileType::HDF5: 0409 case AbstractFileFilter::FileType::NETCDF: 0410 break; 0411 case AbstractFileFilter::FileType::FITS: 0412 icon = QIcon::fromTheme("kstars_fitsviewer"); 0413 break; 0414 case AbstractFileFilter::FileType::JSON: 0415 icon = QIcon::fromTheme("application-json"); 0416 break; 0417 case AbstractFileFilter::FileType::ROOT: 0418 case AbstractFileFilter::FileType::NgspiceRawAscii: 0419 case AbstractFileFilter::FileType::NgspiceRawBinary: 0420 break; 0421 } 0422 0423 return icon; 0424 } 0425 0426 QMenu* LiveDataSource::createContextMenu() { 0427 QMenu* menu = AbstractPart::createContextMenu(); 0428 0429 QAction* firstAction = nullptr; 0430 // if we're populating the context menu for the project explorer, then 0431 //there're already actions available there. Skip the first title-action 0432 //and insert the action at the beginning of the menu. 0433 if (menu->actions().size() > 1) 0434 firstAction = menu->actions().at(1); 0435 0436 menu->insertAction(firstAction, m_plotDataAction); 0437 menu->insertSeparator(firstAction); 0438 0439 return menu; 0440 } 0441 0442 //############################################################################## 0443 //################################# SLOTS #################################### 0444 //############################################################################## 0445 0446 /* 0447 * Called when the watch timer times out, i.e. when modifying the file or directory 0448 * presumably has finished. Also see LiveDataSource::setUpdateType(). 0449 */ 0450 void LiveDataSource::readOnUpdate() { 0451 if (!m_fileSystemWatcher->files().contains(m_fileName)) { 0452 m_fileSystemWatcher->addPath(m_fileName); 0453 QFileInfo file(m_fileName); 0454 if (m_fileSystemWatcher->files().contains(m_fileName)) 0455 m_fileSystemWatcher->removePath(file.absolutePath()); 0456 else { 0457 m_fileSystemWatcher->addPath(file.absolutePath()); 0458 return; 0459 } 0460 } 0461 if (m_paused) 0462 // flag file for reading, once the user decides to continue reading 0463 m_pending = true; 0464 else 0465 read(); 0466 } 0467 0468 /* 0469 * called periodically or on new data changes (file changed, new data in the socket, etc.) 0470 */ 0471 void LiveDataSource::read() { 0472 DEBUG("\nLiveDataSource::read()"); 0473 if (!m_filter) 0474 return; 0475 0476 if (m_reading) 0477 return; 0478 0479 m_reading = true; 0480 0481 //initialize the device (file, socket, serial port) when calling this function for the first time 0482 if (!m_prepared) { 0483 DEBUG(" Preparing device: update type = " << ENUM_TO_STRING(LiveDataSource, UpdateType, m_updateType)); 0484 switch (m_sourceType) { 0485 case SourceType::FileOrPipe: 0486 delete m_device; 0487 m_device = new QFile(m_fileName); 0488 break; 0489 case SourceType::NetworkTcpSocket: 0490 m_tcpSocket = new QTcpSocket(this); 0491 m_device = m_tcpSocket; 0492 m_tcpSocket->connectToHost(m_host, m_port, QIODevice::ReadOnly); 0493 0494 connect(m_tcpSocket, &QTcpSocket::readyRead, this, &LiveDataSource::readyRead); 0495 connect(m_tcpSocket, static_cast<void (QTcpSocket::*) (QAbstractSocket::SocketError)>(&QTcpSocket::error), this, &LiveDataSource::tcpSocketError); 0496 0497 break; 0498 case SourceType::NetworkUdpSocket: 0499 m_udpSocket = new QUdpSocket(this); 0500 m_device = m_udpSocket; 0501 m_udpSocket->bind(QHostAddress(m_host), m_port); 0502 m_udpSocket->connectToHost(m_host, 0, QUdpSocket::ReadOnly); 0503 0504 // only connect to readyRead when update is on new data 0505 if (m_updateType == UpdateType::NewData) 0506 connect(m_udpSocket, &QUdpSocket::readyRead, this, &LiveDataSource::readyRead); 0507 connect(m_udpSocket, static_cast<void (QUdpSocket::*) (QAbstractSocket::SocketError)>(&QUdpSocket::error), this, &LiveDataSource::tcpSocketError); 0508 0509 break; 0510 case SourceType::LocalSocket: 0511 m_localSocket = new QLocalSocket(this); 0512 m_device = m_localSocket; 0513 m_localSocket->connectToServer(m_localSocketName, QLocalSocket::ReadOnly); 0514 0515 connect(m_localSocket, &QLocalSocket::readyRead, this, &LiveDataSource::readyRead); 0516 connect(m_localSocket, static_cast<void (QLocalSocket::*) (QLocalSocket::LocalSocketError)>(&QLocalSocket::error), this, &LiveDataSource::localSocketError); 0517 0518 break; 0519 case SourceType::SerialPort: 0520 #ifdef HAVE_QTSERIALPORT 0521 m_serialPort = new QSerialPort(this); 0522 m_device = m_serialPort; 0523 DEBUG(" Serial: " << STDSTRING(m_serialPortName) << ", " << m_baudRate); 0524 m_serialPort->setBaudRate(m_baudRate); 0525 m_serialPort->setPortName(m_serialPortName); 0526 m_serialPort->open(QIODevice::ReadOnly); 0527 0528 // only connect to readyRead when update is on new data 0529 if (m_updateType == UpdateType::NewData) 0530 connect(m_serialPort, &QSerialPort::readyRead, this, &LiveDataSource::readyRead); 0531 connect(m_serialPort, static_cast<void (QSerialPort::*) (QSerialPort::SerialPortError)>(&QSerialPort::error), this, &LiveDataSource::serialPortError); 0532 #endif 0533 break; 0534 case SourceType::MQTT: 0535 break; 0536 } 0537 m_prepared = true; 0538 } 0539 0540 qint64 bytes = 0; 0541 0542 switch (m_sourceType) { 0543 case SourceType::FileOrPipe: 0544 DEBUG("Reading FileOrPipe. type = " << ENUM_TO_STRING(AbstractFileFilter, FileType, m_fileType)); 0545 switch (m_fileType) { 0546 case AbstractFileFilter::FileType::Ascii: 0547 if (m_readingType == LiveDataSource::ReadingType::WholeFile) { 0548 static_cast<AsciiFilter*>(m_filter)->readFromLiveDevice(*m_device, this, 0); 0549 } else { 0550 bytes = static_cast<AsciiFilter*>(m_filter)->readFromLiveDevice(*m_device, this, m_bytesRead); 0551 m_bytesRead += bytes; 0552 DEBUG("Read " << bytes << " bytes, in total: " << m_bytesRead); 0553 } 0554 break; 0555 case AbstractFileFilter::FileType::Binary: 0556 //TODO: not implemented yet 0557 // bytes = qSharedPointerCast<BinaryFilter>(m_filter)->readFromLiveDevice(*m_file, this, m_bytesRead); 0558 // m_bytesRead += bytes; 0559 case AbstractFileFilter::FileType::ROOT: 0560 case AbstractFileFilter::FileType::NgspiceRawAscii: 0561 case AbstractFileFilter::FileType::NgspiceRawBinary: 0562 //only re-reading of the whole file is supported 0563 m_filter->readDataFromFile(m_fileName, this); 0564 break; 0565 //TODO: other types not implemented yet 0566 case AbstractFileFilter::FileType::Image: 0567 case AbstractFileFilter::FileType::HDF5: 0568 case AbstractFileFilter::FileType::NETCDF: 0569 case AbstractFileFilter::FileType::FITS: 0570 case AbstractFileFilter::FileType::JSON: 0571 break; 0572 } 0573 break; 0574 case SourceType::NetworkTcpSocket: 0575 DEBUG("reading from TCP socket. state before abort = " << m_tcpSocket->state()); 0576 m_tcpSocket->abort(); 0577 m_tcpSocket->connectToHost(m_host, m_port, QIODevice::ReadOnly); 0578 DEBUG("reading from TCP socket. state after reconnect = " << m_tcpSocket->state()); 0579 break; 0580 case SourceType::NetworkUdpSocket: 0581 DEBUG(" Reading from UDP socket. state = " << m_udpSocket->state()); 0582 0583 // reading data here 0584 if (m_fileType == AbstractFileFilter::FileType::Ascii) 0585 static_cast<AsciiFilter*>(m_filter)->readFromLiveDeviceNotFile(*m_device, this); 0586 break; 0587 case SourceType::LocalSocket: 0588 DEBUG(" Reading from local socket. state before abort = " << m_localSocket->state()); 0589 if (m_localSocket->state() == QLocalSocket::ConnectingState) 0590 m_localSocket->abort(); 0591 m_localSocket->connectToServer(m_localSocketName, QLocalSocket::ReadOnly); 0592 if (m_localSocket->waitForConnected()) 0593 m_localSocket->waitForReadyRead(); 0594 DEBUG(" Reading from local socket. state after reconnect = " << m_localSocket->state()); 0595 break; 0596 case SourceType::SerialPort: 0597 DEBUG(" Reading from serial port"); 0598 #ifdef HAVE_QTSERIALPORT 0599 0600 // reading data here 0601 if (m_fileType == AbstractFileFilter::FileType::Ascii) 0602 static_cast<AsciiFilter*>(m_filter)->readFromLiveDeviceNotFile(*m_device, this); 0603 #endif 0604 break; 0605 case SourceType::MQTT: 0606 break; 0607 } 0608 0609 m_reading = false; 0610 } 0611 0612 /*! 0613 * Slot for the signal that is emitted once every time new data is available for reading from the device (not UDP or Serial). 0614 * It will only be emitted again once new data is available, such as when a new payload of network data has arrived on the network socket, 0615 * or when a new block of data has been appended to your device. 0616 */ 0617 void LiveDataSource::readyRead() { 0618 DEBUG("LiveDataSource::readyRead() update type = " << ENUM_TO_STRING(LiveDataSource,UpdateType,m_updateType)); 0619 DEBUG(" REMAINING TIME = " << m_updateTimer->remainingTime()); 0620 0621 if (m_fileType == AbstractFileFilter::FileType::Ascii) 0622 static_cast<AsciiFilter*>(m_filter)->readFromLiveDeviceNotFile(*m_device, this); 0623 0624 //TODO: not implemented yet 0625 // else if (m_fileType == AbstractFileFilter::FileType::Binary) 0626 // dynamic_cast<BinaryFilter*>(m_filter)->readFromLiveDeviceNotFile(*m_device, this); 0627 0628 //since we won't have the timer to call read() where we create new connections 0629 //for sequential devices in read() we just request data/connect to servers 0630 if (m_updateType == UpdateType::NewData) 0631 read(); 0632 } 0633 0634 void LiveDataSource::localSocketError(QLocalSocket::LocalSocketError socketError) { 0635 Q_UNUSED(socketError); 0636 /*disconnect(m_localSocket, SIGNAL(error(QLocalSocket::LocalSocketError)), this, SLOT(localSocketError(QLocalSocket::LocalSocketError))); 0637 disconnect(m_localSocket, SIGNAL(readyRead()), this, SLOT(readyRead()));*/ 0638 0639 /*switch (socketError) { 0640 case QLocalSocket::ServerNotFoundError: 0641 QMessageBox::critical(0, i18n("Local Socket Error"), 0642 i18n("The socket was not found. Please check the socket name.")); 0643 break; 0644 case QLocalSocket::ConnectionRefusedError: 0645 QMessageBox::critical(0, i18n("Local Socket Error"), 0646 i18n("The connection was refused by the peer")); 0647 break; 0648 case QLocalSocket::PeerClosedError: 0649 QMessageBox::critical(0, i18n("Local Socket Error"), 0650 i18n("The socket has closed the connection.")); 0651 break; 0652 default: 0653 QMessageBox::critical(0, i18n("Local Socket Error"), 0654 i18n("The following error occurred: %1.", m_localSocket->errorString())); 0655 }*/ 0656 } 0657 0658 void LiveDataSource::tcpSocketError(QAbstractSocket::SocketError socketError) { 0659 Q_UNUSED(socketError); 0660 /*switch (socketError) { 0661 case QAbstractSocket::ConnectionRefusedError: 0662 QMessageBox::critical(0, i18n("TCP Socket Error"), 0663 i18n("The connection was refused by the peer. Make sure the server is running and check the host name and port settings.")); 0664 break; 0665 case QAbstractSocket::RemoteHostClosedError: 0666 QMessageBox::critical(0, i18n("TCP Socket Error"), 0667 i18n("The remote host closed the connection.")); 0668 break; 0669 case QAbstractSocket::HostNotFoundError: 0670 QMessageBox::critical(0, i18n("TCP Socket Error"), 0671 i18n("The host was not found. Please check the host name and port settings.")); 0672 break; 0673 default: 0674 QMessageBox::critical(0, i18n("TCP Socket Error"), 0675 i18n("The following error occurred: %1.", m_tcpSocket->errorString())); 0676 }*/ 0677 } 0678 0679 #ifdef HAVE_QTSERIALPORT 0680 void LiveDataSource::serialPortError(QSerialPort::SerialPortError serialPortError) { 0681 switch (serialPortError) { 0682 case QSerialPort::DeviceNotFoundError: 0683 QMessageBox::critical(nullptr, i18n("Serial Port Error"), i18n("Failed to open the device.")); 0684 break; 0685 case QSerialPort::PermissionError: 0686 QMessageBox::critical(nullptr, i18n("Serial Port Error"), 0687 i18n("Failed to open the device. Please check your permissions on this device.")); 0688 break; 0689 case QSerialPort::OpenError: 0690 QMessageBox::critical(nullptr, i18n("Serial Port Error"), i18n("Device already opened.")); 0691 break; 0692 case QSerialPort::NotOpenError: 0693 QMessageBox::critical(nullptr, i18n("Serial Port Error"), i18n("The device is not opened.")); 0694 break; 0695 case QSerialPort::ReadError: 0696 QMessageBox::critical(nullptr, i18n("Serial Port Error"), i18n("Failed to read data.")); 0697 break; 0698 case QSerialPort::ResourceError: 0699 QMessageBox::critical(nullptr, i18n("Serial Port Error"), i18n("Failed to read data. The device is removed.")); 0700 break; 0701 case QSerialPort::TimeoutError: 0702 QMessageBox::critical(nullptr, i18n("Serial Port Error"), i18n("The device timed out.")); 0703 break; 0704 #ifndef _MSC_VER 0705 //MSVC complains about the usage of deprecated enums, g++ and clang complain about missing enums 0706 case QSerialPort::ParityError: 0707 case QSerialPort::FramingError: 0708 case QSerialPort::BreakConditionError: 0709 #endif 0710 case QSerialPort::WriteError: 0711 case QSerialPort::UnsupportedOperationError: 0712 case QSerialPort::UnknownError: 0713 QMessageBox::critical(nullptr, i18n("Serial Port Error"), 0714 i18n("The following error occurred: %1.", m_serialPort->errorString())); 0715 break; 0716 case QSerialPort::NoError: 0717 break; 0718 } 0719 } 0720 #endif 0721 0722 void LiveDataSource::plotData() { 0723 auto* dlg = new PlotDataDialog(this); 0724 dlg->exec(); 0725 } 0726 0727 //############################################################################## 0728 //################## Serialization/Deserialization ########################### 0729 //############################################################################## 0730 /*! 0731 Saves as XML. 0732 */ 0733 void LiveDataSource::save(QXmlStreamWriter* writer) const { 0734 writer->writeStartElement("liveDataSource"); 0735 writeBasicAttributes(writer); 0736 writeCommentElement(writer); 0737 0738 //general 0739 writer->writeStartElement("general"); 0740 0741 switch (m_sourceType) { 0742 case SourceType::FileOrPipe: 0743 writer->writeAttribute("fileType", QString::number(static_cast<int>(m_fileType))); 0744 writer->writeAttribute("fileLinked", QString::number(m_fileLinked)); 0745 writer->writeAttribute("relativePath", QString::number(m_relativePath)); 0746 if (m_relativePath) { 0747 //convert from the absolute to the relative path and save it 0748 const Project* p = const_cast<LiveDataSource*>(this)->project(); 0749 QFileInfo fi(p->fileName()); 0750 writer->writeAttribute("fileName", fi.dir().relativeFilePath(m_fileName)); 0751 }else 0752 writer->writeAttribute("fileName", m_fileName); 0753 0754 break; 0755 case SourceType::SerialPort: 0756 writer->writeAttribute("baudRate", QString::number(m_baudRate)); 0757 writer->writeAttribute("serialPortName", m_serialPortName); 0758 break; 0759 case SourceType::NetworkTcpSocket: 0760 case SourceType::NetworkUdpSocket: 0761 writer->writeAttribute("host", m_host); 0762 writer->writeAttribute("port", QString::number(m_port)); 0763 break; 0764 case SourceType::LocalSocket: 0765 break; 0766 case SourceType::MQTT: 0767 break; 0768 } 0769 0770 writer->writeAttribute("updateType", QString::number(static_cast<int>(m_updateType))); 0771 writer->writeAttribute("readingType", QString::number(static_cast<int>(m_readingType))); 0772 writer->writeAttribute("sourceType", QString::number(static_cast<int>(m_sourceType))); 0773 writer->writeAttribute("keepNValues", QString::number(m_keepNValues)); 0774 0775 if (m_updateType == UpdateType::TimeInterval) 0776 writer->writeAttribute("updateInterval", QString::number(m_updateInterval)); 0777 0778 if (m_readingType != ReadingType::TillEnd) 0779 writer->writeAttribute("sampleSize", QString::number(m_sampleSize)); 0780 writer->writeEndElement(); //general 0781 0782 //filter 0783 m_filter->save(writer); 0784 0785 //columns 0786 if (!m_fileLinked) { 0787 for (auto* col : children<Column>(ChildIndexFlag::IncludeHidden)) 0788 col->save(writer); 0789 } 0790 0791 writer->writeEndElement(); // "liveDataSource" 0792 } 0793 0794 /*! 0795 Loads from XML. 0796 */ 0797 bool LiveDataSource::load(XmlStreamReader* reader, bool preview) { 0798 if (!readBasicAttributes(reader)) 0799 return false; 0800 0801 KLocalizedString attributeWarning = ki18n("Attribute '%1' missing or empty, default value is used"); 0802 QXmlStreamAttributes attribs; 0803 QString str; 0804 0805 while (!reader->atEnd()) { 0806 reader->readNext(); 0807 if (reader->isEndElement() 0808 && (reader->name() == "liveDataSource" || reader->name() == "LiveDataSource")) //TODO: remove "LiveDataSources" in couple of releases 0809 break; 0810 0811 if (!reader->isStartElement()) 0812 continue; 0813 0814 if (reader->name() == "comment") { 0815 if (!readCommentElement(reader)) 0816 return false; 0817 } else if (reader->name() == "general") { 0818 attribs = reader->attributes(); 0819 0820 str = attribs.value("fileName").toString(); 0821 if (str.isEmpty()) 0822 reader->raiseWarning(attributeWarning.subs("fileName").toString()); 0823 else 0824 m_fileName = str; 0825 0826 str = attribs.value("fileType").toString(); 0827 if (str.isEmpty()) 0828 reader->raiseWarning(attributeWarning.subs("fileType").toString()); 0829 else 0830 m_fileType = (AbstractFileFilter::FileType)str.toInt(); 0831 0832 str = attribs.value("fileLinked").toString(); 0833 if (str.isEmpty()) 0834 reader->raiseWarning(attributeWarning.subs("fileLinked").toString()); 0835 else 0836 m_fileLinked = str.toInt(); 0837 0838 str = attribs.value("relativePath").toString(); 0839 if (str.isEmpty()) 0840 reader->raiseWarning(attributeWarning.subs("relativePath").toString()); 0841 else 0842 m_relativePath = str.toInt(); 0843 0844 str = attribs.value("updateType").toString(); 0845 if (str.isEmpty()) 0846 reader->raiseWarning(attributeWarning.subs("updateType").toString()); 0847 else 0848 m_updateType = static_cast<UpdateType>(str.toInt()); 0849 0850 str = attribs.value("sourceType").toString(); 0851 if (str.isEmpty()) 0852 reader->raiseWarning(attributeWarning.subs("sourceType").toString()); 0853 else 0854 m_sourceType = static_cast<SourceType>(str.toInt()); 0855 0856 str = attribs.value("readingType").toString(); 0857 if (str.isEmpty()) 0858 reader->raiseWarning(attributeWarning.subs("readingType").toString()); 0859 else 0860 m_readingType = static_cast<ReadingType>(str.toInt()); 0861 0862 if (m_updateType == UpdateType::TimeInterval) { 0863 str = attribs.value("updateInterval").toString(); 0864 if (str.isEmpty()) 0865 reader->raiseWarning(attributeWarning.subs("updateInterval").toString()); 0866 else 0867 m_updateInterval = str.toInt(); 0868 } 0869 0870 if (m_readingType != ReadingType::TillEnd) { 0871 str = attribs.value("sampleSize").toString(); 0872 if (str.isEmpty()) 0873 reader->raiseWarning(attributeWarning.subs("sampleSize").toString()); 0874 else 0875 m_sampleSize = str.toInt(); 0876 } 0877 0878 switch (m_sourceType) { 0879 case SourceType::SerialPort: 0880 str = attribs.value("baudRate").toString(); 0881 if (str.isEmpty()) 0882 reader->raiseWarning(attributeWarning.subs("baudRate").toString()); 0883 else 0884 m_baudRate = str.toInt(); 0885 0886 str = attribs.value("serialPortName").toString(); 0887 if (str.isEmpty()) 0888 reader->raiseWarning(attributeWarning.subs("serialPortName").toString()); 0889 else 0890 m_serialPortName = str; 0891 0892 break; 0893 case SourceType::NetworkTcpSocket: 0894 case SourceType::NetworkUdpSocket: 0895 str = attribs.value("host").toString(); 0896 if (str.isEmpty()) 0897 reader->raiseWarning(attributeWarning.subs("host").toString()); 0898 else 0899 m_host = str; 0900 0901 str = attribs.value("port").toString(); 0902 if (str.isEmpty()) 0903 reader->raiseWarning(attributeWarning.subs("port").toString()); 0904 else 0905 m_host = str; 0906 break; 0907 case SourceType::MQTT: 0908 break; 0909 case SourceType::FileOrPipe: 0910 break; 0911 case SourceType::LocalSocket: 0912 break; 0913 } 0914 0915 } else if (reader->name() == "asciiFilter") { 0916 setFilter(new AsciiFilter); 0917 if (!m_filter->load(reader)) 0918 return false; 0919 } else if (reader->name() == "rootFilter") { 0920 setFilter(new ROOTFilter); 0921 if (!m_filter->load(reader)) 0922 return false; 0923 } else if (reader->name() == "column") { 0924 Column* column = new Column(QString(), AbstractColumn::ColumnMode::Text); 0925 if (!column->load(reader, preview)) { 0926 delete column; 0927 setColumnCount(0); 0928 return false; 0929 } 0930 addChild(column); 0931 } else {// unknown element 0932 reader->raiseWarning(i18n("unknown element '%1'", reader->name().toString())); 0933 if (!reader->skipToEndElement()) return false; 0934 } 0935 } 0936 0937 return !reader->hasError(); 0938 } 0939 0940 void LiveDataSource::finalizeLoad() { 0941 //convert from the relative path saved in the project file to the absolute file to work with 0942 if (m_relativePath) { 0943 QFileInfo fi(project()->fileName()); 0944 m_fileName = fi.dir().absoluteFilePath(m_fileName); 0945 } 0946 0947 //read the content of the file if it was only linked 0948 if (m_fileLinked && QFile::exists(m_fileName)) 0949 this->read(); 0950 0951 //call setUpdateType() to start watching the file for changes, is required 0952 setUpdateType(m_updateType); 0953 }