serialEvent3() only works on the second, third, etc... time

Code:

String cmdData; //Store the complete command on one line to send to sensor board.

String phResponse; //Store the ph sensor response.

boolean startOfLine = false;
boolean endOfLine = false;
boolean stringComplete = false;

boolean s3Trigger = false;

void setup()
{
  Serial3.begin(38400);
  
  Serial.begin(38400);
}

void serialEvent()
{
  while (Serial.available())
  { 
    char cmd = (char)Serial.read();
    
    if (cmd == '{')
    { 
      startOfLine = true;
    }
    
    if (cmd == '}')
    { 
      endOfLine = true;
    }
    
    if (startOfLine && cmd != '{'  && cmd != '}')
    {
      cmdData += cmd;
    }
  }
}

void serialEvent3()
{
  while(Serial3.available())
  {
    char cmd3 = (char)Serial3.read();

    phResponse += String(cmd3);

    if (cmd3 == '\r') //if Carriage Return has been found then...
    {
      stringComplete = true;
    }
  }
}

void loop()
{
  if (startOfLine && endOfLine) //both startOfLine and endOfLine must be true to run the command...
  { 
    startOfLine = false;
    endOfLine = false;
    
    s3Trigger = true; //set the s3Trigger boolean to true to check if data on Serial3.available() is available.
    
    runCommand();
  }
  
  if (stringComplete)
  {
    Serial.println("Stored Response: " + phResponse); //print stored response from ph sensor.
    
    phResponse = ""; //empty phResponse variable to get ready for the next response
    cmdData = ""; //empty phResponse variable to get ready for the next command

    stringComplete = false; //set stringComplete to false
    s3Trigger = false; //set s3Trigger to false so it doesn't continuously loop.
  }

  if (s3Trigger) //if true then continue
  {
    delay(1000); //delay to make sure the Serial3 buffer has all the data
    
    if (!Serial3.available()) //if Serial3 available then execute the runCommand() function
    {
      runCommand();
    }
    else
    {
      s3Trigger = false; //set s3Trigger to false so it doesn't continuously loop.
    }
  }
}

void runCommand()
{
  cmdData.toLowerCase(); //convert cmdData value to lowercase for sanity reasons

  if (cmdData == "ph")
  {  
    ph();
  } 
}

void ph()
{
  Serial.println("Calculating PH sensor value in 3 Seconds");
  
  delay(3000);
  
  Serial3.print("r\r");
}
  if (s3Trigger) //if true then continue
  {
    delay(1000); //delay to make sure the Serial3 buffer has all the data
    
    if (!Serial3.available()) //if Serial3 available then execute the runCommand() function
    {
      runCommand();
    }
    else
    {
      s3Trigger = false; //set s3Trigger to false so it doesn't continuously loop.
    }
  }

serialEvent3() {} will only work on the second, third and etc... time. That if statement will run the previous command sent via serial over the terminal. Once the Serial3 has data in the buffer, any future commands entered will work as expected. Why is my code behaving as such? I'm not sure implementing that if statement is necessary and that my sketch logic is at fault.

Working Code EDIT:

String cmdData; //Store the complete command on one line to send to sensor board.

String phResponse; //Store the ph sensor response.

boolean startOfLine = false;
boolean endOfLine = false;
boolean stringComplete = false;

boolean s3Trigger = false;

void setup()
{
  Serial3.begin(38400);
  
  Serial.begin(38400);
  
  pinMode(2, OUTPUT); //used for temperature probe
}

void runCommand()
{
  cmdData.toLowerCase(); //convert cmdData value to lowercase for sanity reasons
  
  Serial.println("runCommand was executed! cmdData: " + cmdData);

  if (cmdData == "ph")
  {
    Serial.println("ph was found");
    
    //Serial3.print("r\r");
    
    ph();
  }
 
  if (cmdData == "phatc")
  {
    phATC();
  }
}

float getTemp(char tempType)
{
  float v_out;           //voltage output from temp sensor
  float temp;            //the final temperature is stored here (this is only for code clarity)
  float tempInCelcius;   //stores temperature in C
  float tempInFarenheit; //store temperature in F

  digitalWrite(A0, LOW); //set pull-up on analog pin
  digitalWrite(2, HIGH); //set pin 2 high, this will turn on temp sensor
  delay(2);              //wait 1 ms for temp to stabilize

  v_out = analogRead(0); //read the input pin

  digitalWrite(2, LOW); //set pin 2 low, this will turn off temp sensor

  v_out*=.0048;                                        //convert ADC points to volts (we are using .0048 because this device is running at 5 volts)
  v_out*=1000;                                         //convert volts to millivolts

  tempInCelcius = 0.0512 * v_out -20.5128;             //the equation from millivolts to temperature
  tempInFarenheit = (tempInCelcius * 9.0)/ 5.0 + 32.0; // Convert Celcius to Fahrenheit

  if (tempType == 'c')
  { 
    return tempInCelcius; //return temp in celcius
  } 
  else if (tempType == 'f')
  {
    return tempInFarenheit; //return temp in Farenheit
  }
}

void ph()
{
    Serial.println("ph function executed!");
    
    Serial3.print("r\r");
}

void phATC()
{
  Serial.println("PH Auto Temperature Calibration will start in 3 Seconds");
  delay(3000);

  float temp = getTemp('c');
  char tempAr[10];
  String tempAsString;
  String tempData;

  dtostrf(temp,1,2,tempAr);
  tempAsString = String(tempAr);

  tempData = tempAsString + '\r';
  Serial3.print(tempData);
}

void loop()
{
  //while(!Serial.available());
  
  //delay(1000);
  
  while(Serial.available())
  {
    char cmd = (char)Serial.read();
      
    if (cmd == '{')
    { 
      startOfLine = true;
    }
      
    if (cmd == '}')
    { 
      endOfLine = true;
    }
      
    if (startOfLine && (cmd != '{'  && cmd != '}' && cmd != '\r')) // Must add the \r to filter out the CR.
    {
      cmdData += cmd;
    }
  }
  
  if (startOfLine && endOfLine) //both startOfLine and endOfLine must be true to run the command...
  { 
    startOfLine = false;
    endOfLine = false;
    
    s3Trigger = true; //set the s3Trigger boolean to true to check if data on Serial3.available() is available.
    
    //Serial.println("brackets!");
    
    runCommand();
  }
  
  delay(2000);
  
  if(!Serial3.available() && s3Trigger)
  {
    runCommand();
  }else
  {
    while(Serial3.available())
    {
      char cmd3 = (char)Serial3.read();
  
      phResponse += String(cmd3);
  
      if (cmd3 == '\r') //if Carriage Return has been found then...
      {
        stringComplete = true;
      }
    }
  }
  
  if (stringComplete)
  {
    Serial.println("Stored Response: " + phResponse); //print stored response from ph sensor.
      
    phResponse = ""; //empty phResponse variable to get ready for the next response
    cmdData = ""; //empty phResponse variable to get ready for the next command
  
    stringComplete = false; //set stringComplete to false
    s3Trigger = false; //set s3Trigger to false so it doesn't continuously loop.
  }
}

I don't understand your problem.

serialEvent3() {} will only work on the second, third and etc... time.

What does that mean? Do some characters get lost? Describe the situation you would expect from the code and also describe what exactly you get when sending what on the serial line.

BTW: You use delay() several times with values over 1000 (more than a second). Please keep in mind that, given your baudrate, in one second almost 400 characters may be received over the serial interface. The input buffer is not that size, so if you're receiving data on the serial interface during your delay()s you will loose data! Try to get rid of the delay()s completely.

For an example, I would type {ph}. In theory, the {ph} command should trigger a response along the lines of "Stored Response: 9.69" the first time the {ph} command is submitted which that is not the case. The way the sketch is working, if (s3Trigger) will automatically execute runCommand() again otherwise I would have to manually type in {ph} a second or third time to get the response that I am looking for.

d0773d:
For an example, I would type {ph}. In theory, the {ph} command should trigger a response along the lines of "Stored Response: 9.69" the first time the {ph} command is submitted which that is not the case. The way the sketch is working, if (s3Trigger) will automatically execute runCommand() again otherwise I would have to manually type in {ph} a second or third time to get the response that I am looking for.

You need to look at what you're receiving in more detail.

When you send "{ph}", what characters does your sketch receive?
What does it receive when you type "[ph}" the second time? The third time?

PeterH:
You need to look at what you're receiving in more detail.

When you send "{ph}", what characters does your sketch receive?
What does it receive when you type "[ph}" the second time? The third time?

When I send {ph} only "ph" gets sent. The curly brackets are only there to inform the sketch to only store the characters between the curly brackets. When the if statement finds "ph", it calls the ph() function which sends "r\r" to the sensor board. The sensor board will now respond with a float value of lets say "8.55". That value will continuously change depending on the ph of the liquid. I receive a float value the second, third, etc... time. I know the sensor board is responding because it blinks its led on tx and rx.

It would help if I posted the datasheet of the sensor: http://atlas-scientific.com/_files/pH_Circuit_3.0.pdf for a more in depth description of the sensor board: http://atlas-scientific.com/product_pages/kits/ph-kit.html

d0773d:
When I send {ph} only "ph" gets sent.

Either that's a typo, or there is something very strange going on.

Ignore what the command means and what your sketch is expected to do as a result.

Just look at the sequence of ASCII characters that you send and what sequence is received.

You send '{' followed by 'p' followed by 'h' followed by '}'. What characters are received?
Now you send those four characters again. What characters are received?
And you send them a third time: what characters are received then?

Either that's a typo, or there is something very strange going on.

Looking at the code, OP meant that when {ph} was sent, only ph gets stored (not sent).

PaulS:

Either that's a typo, or there is something very strange going on.

Looking at the code, OP meant that when {ph} was sent, only ph gets stored (not sent).

Hi Paul, you are correct by saying that. This is extremely odd why Serial3.available() equals -1 on the initial function ph() call. I would have to call the ph() function a second time to get a readout. BTW I am using a Arduino Mega 2560.

This is extremely odd why Serial3.available() equals -1 on the initial function ph() call.

Serial.available() returns the number of bytes available to be read. It is impossible for the function to return -1.

PaulS:

This is extremely odd why Serial3.available() equals -1 on the initial function ph() call.

Serial.available() returns the number of bytes available to be read. It is impossible for the function to return -1.

Oops, I meant Serial.println(Serial3.read()); prints a -1.

Oops, I meant Serial.println(Serial3.read()); prints a -1.

Well, that is perfectly understandable. If you call Serial.read() when there is nothing to read, the read() method returns -1.

You should only call Serial.read() after using Serial.available() to determine that there is something to read.

PaulS:

Oops, I meant Serial.println(Serial3.read()); prints a -1.

Well, that is perfectly understandable. If you call Serial.read() when there is nothing to read, the read() method returns -1.

You should only call Serial.read() after using Serial.available() to determine that there is something to read.

I have been learning more about Serial on the reference section of arduino.cc. I may have figured out the issue:

old code:
if (startOfLine && cmd != '{' && cmd != '}')
{
cmdData += cmd;
}

new code:
if (startOfLine && (cmd != '{' && cmd != '}' && cmd != '\r'))
{
cmdData += cmd;
}

After 2 days of fussing with this code, I realized every serial terminal program I used appended the transmitting text with a CR. I forgot to filter out the CR before the storage of the command. I also removed all the code from the serialEvent3() {} into the loop. After filtering the cr and moving the serialEvent() the code seems to be working as expected. I still think the code should work correctly in the serialEvent3() and I am still not sure as to why it is not. I edited my original post with the new code.