Akhil Chandra Maganti Akhil Chandra Maganti - 5 months ago 21
Linux Question

Get data from Data reader terminal to a GUI in real time

I am having a data reader which takes the data from a buffer and displays the output to console. Now I want to see the data into a textedit in real time.
I have seen some examples showing how the data can be read from a text file (non real-time).

Please find the attachment.

/* Create a DataReader for the chatMessageTopic Topic (using the appropriate QoS). */
parentReader = chatSubscriber->create_datareader(
chatMessageTopic.in(),
DATAREADER_QOS_USE_TOPIC_QOS,
NULL,
STATUS_MASK_NONE);
checkHandle(parentReader.in(), "DDS::Subscriber::create_datareader");

/* Narrow the abstract parent into its typed representative. */
chatAdmin = Chat::ChatMessageDataReader::_narrow(parentReader.in());
checkHandle(chatAdmin.in(), "Chat::ChatMessageDataReader::_narrow");

/* Print a message that the MessageBoard has opened. */
cout << "MessageBoard has opened: send ChatMessages...." << endl << endl;

while (!terminated) {
/* Note: using read does not remove the samples from
unregistered instances from the DataReader. This means
that the DataRase would use more and more resources.
That's why we use take here instead. */

status = chatAdmin->take(
msgSeq,
infoSeq,
LENGTH_UNLIMITED,
ANY_SAMPLE_STATE,
ANY_VIEW_STATE,
ALIVE_INSTANCE_STATE );
checkStatus(status, "Chat::ChatMessageDataReader::take");

for (DDS::ULong i = 0; i < msgSeq->length(); i++) {
ChatMessage *msg = &(msgSeq[i]);
cout << msg->index << ": " << msg->content << endl;
fflush(stdout);
}

status = chatAdmin->return_loan(msgSeq, infoSeq);
checkStatus(status, "Chat::ChatMessageDataReader::return_loan");

Answer

You likely don't want that your UI is refreshing at a 1000Hz. So there's no point in running this code in a busy loop. Instead, run it at a reasonable refresh rate, e.g. 30Hz, from a timer.

For performance reasons, it may help to emit row batches as well as individual rows for the log:

Let's abstract this out:

class DDSProcessor : public QObject {
  Q_OBJECT
  QBasicTimer m_timer;
  DDS::Subscriber * m_chatSubscriber;
  Chat::ChatMessageDataReader * m_chatDataReader = nullptr;
  ...
  Q_SIGNAL void message(const QString &);
  Q_SIGNAL void messages(const QStringList &);
  void timerEvent(QTimerEvent * ev) {
    if (ev->timerId() != m_timer.timerId()) return;
    auto status = chatAdmin->take(
        msgSeq,
        infoSeq,
        LENGTH_UNLIMITED,
        ANY_SAMPLE_STATE,
        ANY_VIEW_STATE,
        ALIVE_INSTANCE_STATE );
    checkStatus(status, "Chat::ChatMessageDataReader::take");
    QStringList texts;
    for (DDS::ULong i = 0; i < msgSeq->length(); i++) {
       auto msg = &(msgSeq[i]);
       auto text = tr("%1: %2").arg(msg->index).arg(msg->content);
       texts << text;
       emit message(text);
    }
    emit messages(texts);
    status = chatAdmin->return_loan(msgSeq, infoSeq);
    checkStatus(status, "Chat::ChatMessageDataReader::return_loan");
  }
public:
  void start() {
    auto reader = m_chatSubscriber->create_datareader(
      m_chatMessageTopic.in(), ...);
    checkHandle(read.in(), "DDS::Subscriber::create_datareader");
    m_chatDataReader = Chat::ChatMessageDataReader::_narrow(reader.in());
    checkHandler(m_chatDataReader.in(), "Chat::ChatMessageDataReader::_narrow");
    auto text = tr("MessageBoard had opened: send ChatMessages.");
    emit message(text);
    emit messages(text);
    m_timer.start(this, 1000/30);
  }
  ...
};

I'm not familiar with the DDS library enough, but there are some callbacks you should be using to be notified instead, and then you'd emit the signal(s) from the callbacks. That would remove the need for an explicit UI update loop.

All you have to do in the UI, then, is to act on the messages sent out by the DDSProcessor:

void setup(DDSProcessor * src, QTextEdit * dst) {
  connect(src, &DDSProcessor::message, dst, &QTextEdit::append);
}

Note that both QTextEdit and QPlainTextEdit are very slow and unsuitable for fast logging. The easiest workaround is to use a list model with a QListView, and leverage the batched log entry signal:

int main(int argc, char ** argv) {
  QApplication app(argc, argv);
  QStringListModel logModel;
  QListView view;
  DDSProcessor processor;
  QObject::connect(&processor, &DDSProcessor::messages, &logModel, [&](const QStringList & texts){
    auto row = logModel.rowCount();
    logModel.insertRows(row, texts.count());
    for (auto const & text : texts)
      logModel.setData(model.index(row++), text);
  });
  view.setModel(&logModel);
  view.show();
  view.setUniformItemSizes(true); // needed for performance
  ...
  return app.exec();
}
Comments