goulashsoup
goulashsoup

Reputation: 3076

How to send data to tty0tty null modem emulator port opened in QT

I have open working connection to a emulated serial port created with tty0tty nullmodem emulator via QSerialPort and I can write data to it like:

QSerialPort serial_stream;
...
serial_stream.setPortName(QString("/dev/tnt0"));
bool loc = serial_stream.open(serial_stream.ReadWrite)
...
serial_stream.write(buf);

I open a terminal and make a connection to the connected (in this case tnt1) tnt-port (e.g. tnt0 <-> tnt1 are connected) via "screen":

screen /dev/tnt1

When I execute the program I get the expected input (here "buf") on the screen. But how can I send data the way back to the opened port in Qt so that I can do the following to read data:

int size = serial_stream.bytesAvailable();
QByteArray data = serial_stream.read(size);

I already tried to stop the program with "sleep"...:

#include <unistd.h>
...
usleep(15*1000000);

...and send the data to it like this:

echo “TEXT“ > /dev/tnt1

But it does not work. I am glad for your help!

Upvotes: 0

Views: 3789

Answers (1)

goulashsoup
goulashsoup

Reputation: 3076

Sending/Reading data with QSerialPort using tty0tty - null modem emulator (v1.2)

1. Build up the tty0tty-connections:

In two ways it is possible to build up a tty0tty-connection (between two virtual ports). Both ways may require the kernel header files. For the 2. it is definitively necessary otherwise an error while make is inevitable.

(1.) The short way:

  1. Download and extract tty0tty (first link in title).
  2. Open a terminal and cd to [Path to tty0tty-folder]/tty0tty/pts
  3. Execute sudo ./tty0tty

Then the two connected pseudo terminal slaves should be shown:

(/dev/pts/X) <=> (/dev/pts/Y) with X, Y variable.

(2.) the longer way (but more or less permanent):

  1. Follow this Installation guide

Maybe the persisting across boots does not work so it is necessary to repeat the following parts of the Installation guide after reboot:

  1. Load the module
    • sudo depmod
    • sudo modprobe tty0tty
  2. Give appropriate permissions to the new serial ports (native terminal devices)
    • sudo chmod 666 /dev/tnt*

After execute ls /dev/tnt* a list of ports shoud be shown:

/dev/tnt0 ... /dev/tnt7

2. Test if the tty0tty-connection works (optional):

The connection can be tested with cat:

  1. Open a terminal[1] and let show the output of one pseudo/native terminal device:

    • (1.)cat /dev/pts/X or (2.)cat /dev/tnt0
  2. Open another terminal[2] and write to this pseudo/native terminal device:

    • (1.)echo "message" > /dev/pts/Y or (2.)echo "hello" > /dev/tnt1

The output should be shown in terminal[1]:

message

Additional info: To stop cat press: Ctrl + Z

3. Open the terminal devices as ports with QSerialPort in a Qt-Project:

  1. Add the serialport-reference to the Qt-specific configuration options in the [projectname].pro-file:

    QT += serialport

    Additional info: It is allowed to put all references in one line:

    i.e. QT+= core gui serialport ...

  2. Add required header file in the desired [name].h-file:

    #include <QtSerialPort/QSerialPort>

  3. Add two QSerialPort-objects, set the path of the terminal devices via void QSerialPort::setPortName(const QString &name) and open the ports via bool QSerialPort::open(OpenMode mode) with desired permissions (ReadOnly, WriteOnly, ReadWrite). Example:

        std::string tty0ttyPort1 = "/dev/tnt0";// or = "/dev/pts/X
        std::string tty0ttyPort2 = "/dev/tnt1";// or = "/dev/pts/Y
        QSerialPort qport1;
        QSerialPort qport2;
        qport1.setPortName(QString(tty0ttyPort1.c_str()));
        bool isOpen1 = qport1.open(qport1.ReadWrite);//Read and Write permission
        std::cout << "isOpen1: " << isOpen1 << std::endl;
        qport2.setPortName(QString(tty0ttyPort2.c_str()));
        bool isOpen2 = qport2.open(qport2.ReadWrite);
        std::cout << "isOpen2: " << isOpen2 << std::endl;
    

isOpen1 and isOpen2 should be true:

isOpen1: 1
isOpen2: 1

Additional info 1: Maybe is it required to execute the compiled code with super user permission:

  1. Compile the Code.
  2. Open a terminal and cd to [Path to compiled project folder]/[projectname]
  3. Execute sudo [executable_file] or run as super user su (inadvisable)

Additional info 2: If the following warning appears...:

QSocketNotifier: Can only be used with threads started with QThread

...there is no Qt event loop which should allways be in the main()-function:

int main(int argc, char *argv[]){
    QCoreApplication a(argc, argv);
    //My Testcode (Showed above in section 3.3)
    return a.exec();
}

4. Using the opened QSerialPorts:

(1.) Without Signals:

Use qint64 QIODevice::write(const char * data) to write from one QSerialPort to another. Use qint64 QIODevice::read(char * data, qint64 maxSize) or QByteArray QIODevice::readAll() to read the sended data. Here it is import to know that it is necessary to use bool QIODevice::waitForReadyRead(int msecs) after sending the data via QIODevice::write() otherwise it will not work. I recommend also to use bool QIODevice::waitForBytesWritten(int msecs). Example:

    const char* sendMessage = "myMessage";
    std::cout << "sendMessage: " << sendMessage << std::endl;
    qport1.write(sendMessage);
    qport1.waitForBytesWritten(2000);
    qport2.waitForReadyRead(2000);//required
    const char* readMessage = qport2.readAll().data();
    std::cout << "readMessage: " << readMessage << std::endl;

Should give the output:

readMessage: myMessage

If it is desired to read a defined number of bytes use QIODevice::read():

    const char* readMessage = qport2.read(5);
    std::cout << "readMessage: " << readMessage << std::endl;

Should give the output:

readMessage: myMess

Additional info: To check how many bytes are available to read use qint64 QIODevice::bytesAvailable() const. Example:

std::cout << qport2.bytesAvailable() << std::endl;

(2.) With Signals:

In the constructor of a Class it is possible to connect a private slot to the readyRead() signal using QObject::connect():

Classname::Classname(){
    ...
    QObject::connect(&qport1, SIGNAL(readyRead()), this, SLOT(myPrivateSlotFunction()));
    //with "qport1" as sender and "this" as receiver
}

...

Classname::myPrivateSlotFunction(){
    ...
    const char* readMessage = qport1.readAll().data();
    ...
}

Upvotes: 3

Related Questions