c++qtarduinoqt5qtserialport

QSerialPort has bytes available but can't read


I'm writing a Qt GUI application that receives and sends data to an Arduino over serial. It writes correctly, but when trying to read it doesn't work.

My problem is:

I have an Arduino code that sends things over Serial, I programmed it to turn on and off a LED on pin 13 depending on the received data:

#define ARDUINO_TURNED_LED_ON "LEDON"
#define ARDUINO_TURNED_LED_OFF "LEDOFF"
#define PC_REQUESTED_LED_STATE_CHANGE u8"CHANGELED"

void setup() 
{
 // put your setup code here, to run once:
 Serial.begin(9600);
 pinMode(13, OUTPUT);
}

String serialCmd = "";
bool ledState=false;

void loop()
{
 // put your main code here, to run repeatedly:
 if (Serial.available())
 {
  serialCmd=Serial.readString();

  if (serialCmd==PC_REQUESTED_LED_STATE_CHANGE)
  {
   if (ledState)
   {
     digitalWrite(13, LOW);
     Serial.write(ARDUINO_TURNED_LED_OFF);
   }
   else
   {
    digitalWrite(13, HIGH);
    Serial.write(ARDUINO_TURNED_LED_ON);
   };

   ledState=!ledState;
   serialCmd = "";

  }  

 }

}

I'm using the following signal and slot at MainWindow class:

#define ARDUINO_TURNED_LED_ON "LEDON"
#define ARDUINO_TURNED_LED_OFF "LEDOFF"
#define PC_REQUESTED_LED_STATE_CHANGE u8"CHANGELED"

MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
    ui->setupUi(this);

/* Create Object the Class QSerialPort*/
    serialPort = new QSerialPort(this);

    /* Create Object the Class Arduino to manipulate read/write*/
    arduino = new Arduino(serialPort);
    
    //connect signal:
    connect(serialPort, SIGNAL(readyRead()), this, SLOT(ReadData()));
}

//slot
void MainWindow::ReadData()
{
    QString received = arduino->Read();
    qDebug() << "received data:" << received;

    if (received==ARDUINO_TURNED_LED_ON)
    {
        qDebug() << "led on";
    }
    else if (received==ARDUINO_TURNED_LED_OFF)
    {
        qDebug() << "led off";
    }
}

And the Arduino::Read() method that is called by the previous slot:

QString Arduino::Read()
{
 QString bufRxSerial;

 qDebug() << "bytes available:" << serialPort->bytesAvailable();

 /* Awaits read all the data before continuing */
 while (serialPort->waitForReadyRead(20)) {
     bufRxSerial += serialPort->readAll();
 }
 return bufRxSerial;
}

When writing to the Arduino it works well, the LED changes as it should, the problem happens when Qt tries to read the replied data.

In most of the times that the Arduino sends something, the signal is emitted and serialPort->bytesAvailable() returns a number that is not zero, but serialPort->waitForReadyRead(20) times out without receiving anything and serialPort->readAll() returns an empty QString. If I use serialPort->waitForReadyRead(-1) the window freezes.

The weirdest thing is that, in a random moment that something is sent, everything works well and the function returns all the data that couldn't be read previously.

Here's an example of what happens:

1st attempt (fails)

Qt writes PC_REQUESTED_LED_STATE_CHANGE (u8"CHANGELED") to the serial port

Arduino LED changes its state (turns on)

Arduino writes ARDUINO_TURNED_LED_ON ("LEDON") to the serial port

Qt reads an empty QString from the Arduino

2nd attempt (fails)

Qt writes PC_REQUESTED_LED_STATE_CHANGE (u8"CHANGELED") to the serial port

Arduino LED changes its state (turns off)

Arduino writes ARDUINO_TURNED_LED_OFF ("LEDOFF") to the serial port

Qt reads an empty QString from the Arduino

3rd attempt (this is a random attempt that everything works)

Qt writes PC_REQUESTED_LED_STATE_CHANGE (u8"CHANGELED") to the serial port

Arduino LED changes its state (turns on)

Arduino writes ARDUINO_TURNED_LED_ON ("LEDON") to the serial port

Qt reads "LEDONLEDOFFLEDON" from the Arduino, that is everything that couldn't be read previosly.

4th attempt (fails)

Qt writes PC_REQUESTED_LED_STATE_CHANGE (u8"CHANGELED") to the serial port

Arduino LED changes its state (turns off)

Arduino writes ARDUINO_TURNED_LED_OFF ("LEDOFF") to the serial port

Qt reads an empty QString from the Arduino

5th attempt (this is another random attempt that everything works)

Qt writes PC_REQUESTED_LED_STATE_CHANGE (u8"CHANGELED") to the serial port

Arduino LED changes its state (turns on)

Arduino writes ARDUINO_TURNED_LED_ON ("LEDON") to the serial port

Qt reads "LEDOFFLEDON" from the Arduino, that is everything that couldn't be read previosly.

I tested using the Arduino IDE serial monitor and it worked as it was supposed to be: I wrote "CHANGELED", then the LED changed and the Arduino replied "LEDON" or "LEDOFF" all the times.

What can I do to solve this problem? Thanks

Edit: the complete code can be found here


Solution

  • The Read method is incorrect and unnecessary. You should never use the waitFor methods. You also shouldn't be using QString when dealing with simple ASCII data that QByteArray handles fine:

    class MainWindow : ... {
      QByteArray m_inBuffer;
      ...
    };
    
    void MainWindow::ReadData() {
      m_inBuffer.append(arduino->readAll());
      QByteArray match;
      while (true) {
        if (m_inBuffer.startsWith((match = ARDUINO_TURNED_LED_ON))) {
          qDebug() << "led on";
        } else if (m_inBuffer.startsWith((match = ARDUINO_TURNED_LED_OFF))) {
          qDebug() << "led off";
        } else {
          match = {};
          break;
        }
      }
      m_inBuffer.remove(0, match.size());
    }
    

    The deal is simple: ReadData can be called with any number of bytes available - even a single byte. Thus you must accumulate data until a full "packet" is received. In your case, packets can only be delineated by matching a full response string.

    You'd make your life much easier if Arduino sent full lines - replace Serial.write with Serial.println. Then the lines are packets, and you don't need to buffer the data yourself:

    void MainWindow::ReadData() {
      while (arduino->canReadLine()) {
        auto line = arduino->readLine();
        line.chop(1); // remove the terminating '\n'
        QDebug dbg; // keeps everything on a single line
        dbg << "LINE=" << line;
        if (line == ARDUINO_TURNED_LED_ON)
          dbg << "led on";
        else if (line == ARDUINO_TURNED_LED_OFF)
          dbg << "led off";
      }
    }
    

    See? It's much simpler that way.

    You can also leverage Qt to "simulate" Arduino code without running real Arduino hardware, see this answer for an example.