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