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 }