blackistheanswer
blackistheanswer

Reputation: 127

Qt - get data and forward them on serial connection

I am trying to develop a simple Qt application. After I press a "START" button, the application should continuosly retrieves data from a device (using third party libraries) and forward them as soon as possible on a serial connection.

The (ugly) software I used till now was a console application that ran in a sequential way and got data frame as soon as they are made available by the host, using the following cycle:

    while(1)
    {
          [...]
        while( MyClient.GetFrame().Result != Result::Success )
        {
            Sleep( 200 );
            std::cout << ".";
        }

          [... pack and send on serial]
    }

I was wondering which is the more convinient way to implement this in Qt, in order to keep the GUI responsive but also with the minimum possible latency between getFrame and the serial write function.

Should I use a timer triggered SLOT? QtConcurrent namespace? QRunnable? Which are the main advantages and disadvantages of each of these approaches?

Thanks for your help!

Upvotes: 2

Views: 165

Answers (1)

Since the existing library forces you to poll for data, the only thing to do is to run it on a timer. It's your choice as to if the object that does this job will run in the main thread, or in a worker thread. There's no need to use Qt Concurrent nor QRunnable - using a QObject makes life somewhat simpler since you can easily provide feedback to the GUI.

For example, making some assumptions about your client's API:

class Worker : public QObject {
  Client m_client;
  QSerialPort m_port;
  QBasicTimer m_timer;

  void processFrame() {
    if (m_client.GetFrame().Result != Result::Success) return;
    QByteArray frame = QByteArray::fromRawData(
      m_client.GetFrame().Data, m_client.GetFrame().Size);
    ... process the frame
    if (m_port.write(frame) != frame.size()) {
      ... process the error
    }
  }
  void timerEvent(QTimerEvent * ev) {
    if (ev->timerId() == m_timer.timerId()) processFrame();
  }
public:
  Worker(QObject * parent = 0) : QObject(parent) {}
  Q_SLOT bool open(const QString & name) {
    m_port.close();
    m_port.setPortName(name);
    if (!m_port.open(name)) return false;
    if (!m_port.setBaudRate(9600)) return false;
    if (!m_port.setDataBits(QSerialPort::Data8)) return false;
    ... other settings go here
    return true;
  }
  Q_SLOT void start() { m_timer.start(200, this); }
  Q_SLOT void stop() { m_timer.stop(); }
  ...
}

/// A thread that's always safe to destruct
class Thread : public QThread {
  using QThread::run; // lock the default implementation
public:
  Thread(QObject * parent = 0) : QThread(parent) {}
  ~Thread() { quit(); wait(); }
};

int main(int argc, char ** argv) {
  QApplication app(argc, argv);
  Worker worker;
  Thread thread; // worker must be declared before thread!
  if (true) {
    // this code is optional, disabling it should not change things
    // too much unless the client implementation blocks too much
    thread.start();
    worker.moveToThread(&thread);
  }

  QPushButton button("Start");
  QObject::connect(&button, &QPushButton::clicked, [&worker]{
    // Those are cross-thread calls, they can't be done directly
    QMetaObject::invoke(&worker, "open", Q_ARG(QString, "COM1");
    QMetaObject::invoke(&worker, "start");
  });
  button.show();

  return app.exec(argc, argv);
}

Upvotes: 1

Related Questions