Reading Serial output of multiple sensors of the same type

Hello everybody,

I've read the great tutorial about Serial Input Basics and basically based my code on the examples in this thread. I'm using C strings to collect the measurement data from the sensors. The datasheet for the sensor types I'm using is in the attachement,

Reading a single sensor, there is no problem (Measurement String: SN, PPB, T (°C), RH (%), ADC Raw, T Raw, RH Raw, Day, Hour, Minute, Second ):

16:53:15.981 -> Read NO2 Sensor:
16:53:16.015 -> Newdata is collected ...
16:53:16.015 -> This just in ... 061318011256, 23, 23, 47, 32671, 26071, 28118, 00, 00, 00, 10

16:53:16.117 -> Gasconc.: 23
16:53:16.117 -> Temp. : 23
16:53:16.151 -> Humid. : 47

but as soon as I'm reading 2 sensors, my output looks like that:

17:03:01.158 -> Read NO2 Sensor:
17:03:01.158 -> Newdata is collected ...
17:03:01.192 -> This just in ... 012919012048, 5450, 22, 53, 13253, 25725, 31014, 00, 00, 09, 22061318011256, 2, 22, 49, 32685, 25746, 28858, 00, 00, 10, 15

17:03:01.331 -> Gasconc.: 5450
17:03:01.365 -> Temp. : 22
17:03:01.365 -> Humid. : 53
17:03:01.399 -> Read CO Sensor:
17:03:01.399 -> -----------------------------------
17:03:03.164 -> Read NO2 Sensor:
17:03:03.164 -> Newdata is collected ...
17:03:03.198 -> This just in ... 012919012048, 5450, 22, 53, 13253, 25725, 31006, 00, 00, 09, 23061318011256, 2, 22, 49, 32685, 25745, 28862, 00, 00, 10, 17

17:03:03.334 -> Gasconc.: 5450
17:03:03.368 -> Temp. : 22
17:03:03.368 -> Humid. : 53
17:03:03.402 -> Read CO Sensor:
17:03:03.402 -> -----------------------------------

I would think, that old data in the C string named receivedChars gets overwritten with every new function call of ReadSPECSensor(), but it doesnt seem to be the case, but I'm not sure..... ndx gets set to 0 so I would think after receiving \n - the newline character - the array would be filled from the beginning again, when I read the sensor again.

I use the same function for both sensors. According to the datasheet, it can take up to 1 second, till a measurement string arrives. I'm not sure, if a speed issue in regards of the serial port is the problem. Would it be better, if I use a C string for every sensor instead of only using one for saving the data? My code is below and thanks for any input!

/*

 * Datasheet for commands and for format of the output string: 
 * http://www.spec-sensors.com/wp-content/uploads/2017/01/DG-SDK-968-045_9-6-17.pdf
 *
Script is used for the following objectives: 
- Putting out a stream of continous measurements
- saving the measurements in variables
*/

// Simulated measurement intervall
unsigned long startMillis;  
const unsigned long measurementPeriod = 2000;

HardwareSerial& no2Port = Serial1;
HardwareSerial& coPort = Serial2;

const byte numChars = 128;
char receivedChars[numChars];
char tempChars[numChars];        // temporary array for use when parsing

// variables to hold the parsed data
int no2;
int no2temp;
int no2hum;

int co;
int cotemp;
int cohum;

boolean newData = false;

//============

void setup() {
    Serial.begin(9600);
    no2Port.begin(9600);
    coPort.begin(9600);
    Serial.println("Demo for collecting and parsing measurement string from a SPEC sensor.");
    Serial.println("Based on the Serial Input Basics tutorial in the Arduino forum.");
    Serial.println();
    flush_serial(no2Port);
    flush_serial(coPort);

}

//============

void loop() {
  
  if (millis() - startMillis >= measurementPeriod) {
    startMillis = millis();
    Serial.println("Read NO2 Sensor:");
    ReadSPECSensor(no2, no2temp, no2hum, no2Port);
    Serial.println("Read CO Sensor:");
    ReadSPECSensor(co, cotemp, cohum, coPort);
    Serial.println("-----------------------------------");
  }
}

//============

void recvWithEndMarker(HardwareSerial& gasPort) {
    static byte ndx = 0;
    char endMarker = '\n';
    char rc;
   
    while (gasPort.available() > 0 && newData == false) {
        rc = gasPort.read();

        if (rc != endMarker) {
            receivedChars[ndx] = rc;
            ndx++;
            if (ndx >= numChars) {
                ndx = numChars - 1;
            }
        }
        else {
            receivedChars[ndx] = '\0'; // terminate the string
            ndx = 0;
            newData = true;
            Serial.println("Newdata is collected ...");
        }
    }
}

//============

void parseData(int &sensorGas, int &sensorTemp, int &sensorHum) {      // split the data into its parts

    char * strtokIndx; // this is used by strtok() as an index
    
    strtokIndx = strtok(tempChars,",");      // get the first part - the string // http://www.c-howto.de/tutorial/strings-zeichenketten/string-funktionen/string-zerteilen/
    strtokIndx = strtok(NULL, ","); 
    sensorGas = atoi(strtokIndx);     // convert this part to an integer
    strtokIndx = strtok(NULL, ",");
    sensorTemp = atoi(strtokIndx);     // convert this part to an integer
    strtokIndx = strtok(NULL, ",");
    sensorHum = atoi(strtokIndx);     // convert this part to an integer

}

//============

void showParsedData(int gas, int temp, int hum) {
    Serial.print("Gasconc.: ");
    Serial.println(gas);
    Serial.print("Temp. : ");
    Serial.println(temp);
    Serial.print("Humid. : ");
    Serial.println(hum);
    if (gas > 10000 || temp > 1000 || hum > 1000) { // in the case received string was wrongly read (mixed up)
      Serial.println("Stop exexcution");
      stop();
    }

}

void showNewData() {
        Serial.print("This just in ... ");
        Serial.println(receivedChars);
}

void ReadSPECSensor(int &gas, int &temp, int &hum, HardwareSerial& gasPort) {
  gasPort.print('\r');  // print to gasport to get single measurement string 
  recvWithEndMarker(gasPort);
  if (newData == true) {
        strcpy(tempChars, receivedChars);
            // this temporary copy is necessary to protect the original data
            // because strtok() used in parseData() replaces the commas with \0
        showNewData();    
        parseData(gas, temp, hum);
        showParsedData(gas, temp, hum);
        newData = false;
  }
}

void flush_serial(HardwareSerial& serialPort) {
  
  // Do we have data in the serial input buffer?
  // If so, flush it
  Serial.println("Flush the serial input buffer!");  
  while (serialPort.available() > 0) {
    serialPort.read();
    Serial.println("in while loop!");  
  }
Serial.println("Serial flushed! ");
}


void stop() {
  while(1);
}

EDIT (08.11.2020):

MORE ABOUT THE TOPIC HERE:

https://forum.arduino.cc/index.php?topic=712737.0

DG-SDK-968-045_9-6-17.pdf (565 KB)

Would it be better, if I use a C string for every sensor instead of only using one for saving the data?

Yes. Serial data transmission is asynchronous. If there's a moment when there's nothing available you'll skip to the other port and get a combination of the responses in the buffer.

wildbill:
Yes. Serial data transmission is asynchronous. If there's a moment when there's nothing available you'll skip to the other port and get a combination of the responses in the buffer.

Thank you for the quick reply! I actually didnt know about the fact in regards to skipping to other ports.....
Can I reference these C strings? If I wouldn't do that, I think I have to write a new read function for every single sensor, which is not really practical. I would like to pass my C strings to the function ReadSPECSensor().

SciWax:
Thank you for the quick reply! I actually didnt know about the fact in regards to skipping to other ports.....
Can I reference these C strings? If I wouldn't do that, I think I have to write a new read function for every single sensor, which is not really practical. I would like to pass my C strings to the function ReadSPECSensor().

I've tried to pass the C string as a reference, but it didnt work. receivedChars has to be a parameter for the function recvWithEndMarker(), where the C string gets filled with the data. Afterwards the function has to pass receivedChars back one level higher in the function readSPECSensor(), so it can get parsed with parseData().

I've changed the code, but without passing the C string receivedChars() back up, I only get the same measurement string over and over, which is spottable through the seconds at the end of the string never changing:

18:19:33.220 -> Read NO2 Sensor:
18:19:33.220 -> Newdata is collected ...
18:19:33.254 -> This just in ... 061318011256, 7, 21, 50, 32682, 25500, 29842, 00, 00, 31, 07


18:19:33.322 -> Gasconc.: 7
18:19:33.356 -> Temp. : 21
18:19:33.356 -> Humid. : 50
18:19:33.390 -> Read CO Sensor:
18:19:33.390 -> -----------------------------------
18:19:35.190 -> Read NO2 Sensor:
18:19:35.224 -> Newdata is collected ...
18:19:35.257 -> This just in ... 061318011256, 7, 21, 50, 32682, 25500, 29842, 00, 00, 31, 07


18:19:35.325 -> Gasconc.: 7
18:19:35.359 -> Temp. : 21
18:19:35.359 -> Humid. : 50
18:19:35.359 -> Read CO Sensor:
18:19:35.393 -> -----------------------------------
18:19:37.193 -> Read NO2 Sensor:
18:19:37.227 -> Newdata is collected ...
18:19:37.261 -> This just in ... 061318011256, 7, 21, 50, 32682, 25500, 29842, 00, 00, 31, 07


18:19:37.328 -> Gasconc.: 7
18:19:37.328 -> Temp. : 21
18:19:37.362 -> Humid. : 50
18:19:37.362 -> Read CO Sensor:
18:19:37.397 -> -----------------------------------
18:19:39.193 -> Read NO2 Sensor:
18:19:39.228 -> Newdata is collected ...
18:19:39.228 -> This just in ... 061318011256, 7, 21, 50, 32682, 25500, 29842, 00, 00, 31, 07


18:19:39.329 -> Gasconc.: 7
18:19:39.329 -> Temp. : 21
18:19:39.363 -> Humid. : 50
18:19:39.363 -> Read CO Sensor:
18:19:39.398 -> -----------------------------------

Here is the changes I've made so far (Creating C strings for the 2 sensors and changes to the parameters for the function calls, using references (&) for instance):

*/
// Simulated measurement intervall
unsigned long startMillis;  
const unsigned long measurementPeriod = 2000;

HardwareSerial& no2Port = Serial1;
HardwareSerial& coPort = Serial2;

const byte numChars = 128;
//char receivedChars[numChars];
//char tempChars[numChars];        // temporary array for use when parsing
char receivedCharsNO2[numChars];
char tempCharsNO2[numChars];
char receivedCharsCO[numChars];
char tempCharsCO[numChars];
// variables to hold the parsed data

int no2;
int no2temp;
int no2hum;

int co;
int cotemp;
int cohum;

boolean newData = false;

//============

void setup() {
    Serial.begin(9600);
    no2Port.begin(9600);
    coPort.begin(9600);
    Serial.println("Demo for collecting and parsing measurement string from a SPEC sensor.");
    Serial.println("Based on the Serial Input Basics tutorial in the Arduino forum.");
    Serial.println();
    flush_serial(no2Port);
    flush_serial(coPort);

}

//============

void loop() {
  
  if (millis() - startMillis >= measurementPeriod) {
    startMillis = millis();
    Serial.println("Read NO2 Sensor:");
    ReadSPECSensor(receivedCharsNO2, tempCharsNO2, no2, no2temp, no2hum, no2Port);
    Serial.println("Read CO Sensor:");
    ReadSPECSensor(receivedCharsCO, tempCharsCO, co, cotemp, cohum, coPort);
    Serial.println("-----------------------------------");
  }
}

//============

void recvWithEndMarker(char receivedChars[], HardwareSerial& gasPort) {
    static byte ndx = 0;
    char endMarker = '\n';
    char rc;
   
    while (gasPort.available() > 0 && newData == false) {
        rc = gasPort.read();

        if (rc != endMarker) {
            receivedChars[ndx] = rc;
            ndx++;
            if (ndx >= numChars) {
                ndx = numChars - 1;
            }
        }
        else {
            receivedChars[ndx] = '\0'; // terminate the string
            ndx = 0;
            newData = true;
            Serial.println("Newdata is collected ...");
        }
    }
}

//============

void parseData(char tempChars[], int &sensorGas, int &sensorTemp, int &sensorHum) {      // split the data into its parts

    char * strtokIndx; // this is used by strtok() as an index
    
    strtokIndx = strtok(tempChars,",");      // get the first part - the string // http://www.c-howto.de/tutorial/strings-zeichenketten/string-funktionen/string-zerteilen/
    strtokIndx = strtok(NULL, ","); 
    sensorGas = atoi(strtokIndx);     // convert this part to an integer
    strtokIndx = strtok(NULL, ",");
    sensorTemp = atoi(strtokIndx);     // convert this part to an integer
    strtokIndx = strtok(NULL, ",");
    sensorHum = atoi(strtokIndx);     // convert this part to an integer

}

//============

void showParsedData(int gas, int temp, int hum) {
    Serial.print("Gasconc.: ");
    Serial.println(gas);
    Serial.print("Temp. : ");
    Serial.println(temp);
    Serial.print("Humid. : ");
    Serial.println(hum);
    if (gas > 10000 || temp > 1000 || hum > 1000) { // in the case received string was wrongly read (mixed up)
      Serial.println("Stop exexcution");
      stop();
    }

}

void showNewData(char receivedChars[]) {
        Serial.print("This just in ... ");
        Serial.println(receivedChars);
}

void ReadSPECSensor(char receivedChars[], char tempChars[], int &gas, int &temp, int &hum, HardwareSerial& gasPort) {
  gasPort.print('\r');  // p[rint to gasport to get single measurement string 
  recvWithEndMarker(receivedChars, gasPort);
  if (newData == true) {
        strcpy(tempChars, receivedChars);
            // this temporary copy is necessary to protect the original data
            // because strtok() used in parseData() replaces the commas with \0
        showNewData(receivedChars);    
        parseData(tempChars, gas, temp, hum);
        showParsedData(gas, temp, hum);
        newData = false;
  }
}

void flush_serial(HardwareSerial& serialPort) {
  
  // Do we have data in the serial input buffer?
  // If so, flush it
  Serial.println("Flush the serial input buffer!");  
  while (serialPort.available() > 0) {
    serialPort.read();
    Serial.println("in while loop!");  
  }
Serial.println("Serial flushed! ");
}


void stop() {
  while(1);
}

When I do the following:

void recvWithEndMarker(char &receivedChars[], HardwareSerial& gasPort) { 
  Code
}

I get this error:

exit status 1
declaration of 'receivedChars' as array of references

The key to success in your project is knowing when you have received a complete message from your sensor. Do you know this? IS the message always a fixed number of characters? Is there a defined end of message character?

Finally, is the message length shorter than the buffer used by the serial software?

IF you can do the above, then just read one serial until you get the full message. Any message on the other port will just fill it's buffer and be there when you want to read it. When one serial buffer is read, switch to the other. No need to try to interleave reading from each serial port.

Paul

Paul_KD7HB:
The key to success in your project is knowing when you have received a complete message from your sensor. Do you know this? IS the message always a fixed number of characters? Is there a defined end of message character?

Finally, is the message length shorter than the buffer used by the serial software?

IF you can do the above, then just read one serial until you get the full message. Any message on the other port will just fill it's buffer and be there when you want to read it. When one serial buffer is read, switch to the other. No need to try to interleave reading from each serial port.

Paul

Hello Paul,
thank you for the input!

There is a newline character. I'm using the newline character to know, when the string is finished. I have an example, where I initially used readstringuntil('\n') for this sensor type and I also used it to see, if it works consistently with 3 sensors. readstringuntil worked reliably. I want to avoid using this function though, because it's blocking as far as I know.

The message has not a fixed amount of characters, because the measurements change over time, but there is a limit, which depends on the measurement range of the sensors.

Yes, the message is shorter than the buffer. Only if the sensor creators decided to put empty characters after the measurement string, it would be the case. 128 bytes should be plenty enough. My problems even happened, when I've used 256 bytes and so on. I've also posted the receive outputs from the sensor, which show measurement strings I receive by the sensor.

Regarding the last part of your message: I'm pretty sure, that I'm doing that with my code.

Here is the Code, I've used originally for reading data from the serial port:

String ReadSPEC(HardwareSerial& serialPort) {
  // Clear the data string
  String dataString = "";
  // Now we trigger a measurement
  serialPort.print("\r");
  // We wait for the sensor to respond
  dataString = serialPort.readStringUntil('\n');
  //Serial.println(dataString);
  return dataString;
}

This is not so great, because I'm destroying and re-creating Strings this way.

Just do this:

void recvWithEndMarker(char *receivedChars, HardwareSerial& gasPort) { 
  Code
}

wildbill:
Just do this:

void recvWithEndMarker(char *receivedChars, HardwareSerial& gasPort) { 

Code
}

Thank you! The error message is gone, but there is still no update of the measurement strings. It always shows the same one like mentioned in this post of mine: https://forum.arduino.cc/index.php?topic=709555.msg4767728#msg4767728

At the moment, I'm trying the approach mentioned in this thread and it seems to work so far: https://forum.arduino.cc/index.php?topic=633370.0

The serial monitor output:

-----------------------------------
Read NO2 Sensor:
New data on Serial1 is collected ...
This just in ... 061318011256, 7, 19, 47, 32682, 24562, 27974, 00, 00, 09, 21

Gasconc.: 7
Temp. : 19
Humid. : 47
Read CO Sensor:
New data on Serial2 is collected ...
This just in ... 012919012048, 50, 19, 51, 13033, 24605, 29934, 00, 00, 08, 34

Gasconc.: 50
Temp. : 19
Humid. : 51
-----------------------------------
Read NO2 Sensor:
New data on Serial1 is collected ...
This just in ... 061318011256, 7, 19, 47, 32682, 24562, 27970, 00, 00, 09, 23

Gasconc.: 7
Temp. : 19
Humid. : 47
Read CO Sensor:
New data on Serial2 is collected ...
This just in ... 012919012048, 50, 19, 51, 13033, 24605, 29942, 00, 00, 08, 36

Gasconc.: 50
Temp. : 19
Humid. : 51
-----------------------------------

The code:

// Simulated measurement intervall
unsigned long startMillis;  
const unsigned long measurementPeriod = 2000;


HardwareSerial& no2Port = Serial1;
HardwareSerial& coPort = Serial2;


const byte numCharsSerial1 = 150; //define size of receive buffer
char receivedCharsSerial1[numCharsSerial1]; // an array to store the received data
char tempCharsSerial1[numCharsSerial1]; 
bool newDataSerial1 = false;


const byte numCharsSerial2 = 150; //define size of receive buffer
char receivedCharsSerial2[numCharsSerial2]; // an array to store the received data
char tempCharsSerial2[numCharsSerial2]; 
bool newDataSerial2 = false;


int no2;
int no2temp;
int no2hum;


int co;
int cotemp;
int cohum;


//============


void setup() {
    Serial.begin(9600);
    no2Port.begin(9600);
    coPort.begin(9600);
    Serial.println("Demo for collecting and parsing measurement string from a SPEC sensor.");
    Serial.println("Based on the Serial Input Basics tutorial in the Arduino forum.");
    Serial.println();
    flush_serial(no2Port);
    flush_serial(coPort);


}


//============


void loop() {
  
  if (millis() - startMillis >= measurementPeriod) {
    startMillis = millis();
    Serial.println("Read NO2 Sensor:");
    ReadSPECSensorSerial1(no2, no2temp, no2hum, no2Port);
    Serial.println("Read CO Sensor:");
    ReadSPECSensorSerial2(co, cotemp, cohum, coPort);
    Serial.println("-----------------------------------");
  }
}


//============


// Functions for NO2 serial read
void recvWithEndMarkerSerial1(HardwareSerial& gasPort) {
    static byte ndxSerial1 = 0;
    char endMarkerSerial1 = '\n';
    char rcSerial1;
   
    while (gasPort.available() > 0 && newDataSerial1 == false) {
        rcSerial1 = gasPort.read();


        if (rcSerial1 != endMarkerSerial1) {
            receivedCharsSerial1[ndxSerial1] = rcSerial1;
            ndxSerial1++;
            if (ndxSerial1 >= numCharsSerial1) {
                ndxSerial1 = numCharsSerial1 - 1;
            }
        }
        else {
            receivedCharsSerial1[ndxSerial1] = '\0'; // terminate the string
            ndxSerial1 = 0;
            newDataSerial1 = true;
            Serial.println("New data on Serial1 is collected ...");
        }
    }
}


//============


void parseDataSerial1(int &sensorGasSerial1, int &sensorTempSerial1, int &sensorHumSerial1) {      // split the data into its parts


    char * strtokIndxSerial1; // this is used by strtok() as an index
    
    strtokIndxSerial1 = strtok(tempCharsSerial1,",");      // get the first part - the string // http://www.c-howto.de/tutorial/strings-zeichenketten/string-funktionen/string-zerteilen/
    strtokIndxSerial1 = strtok(NULL, ","); // commented out, because of using Serialnumber of Sensor as startMarker
                                      // uncomment, when using recvWithEndMarker() instead of recvWithStartEndMarker()
    sensorGasSerial1 = atoi(strtokIndxSerial1);     // convert this part to an integer
    strtokIndxSerial1 = strtok(NULL, ",");
    sensorTempSerial1 = atoi(strtokIndxSerial1);     // convert this part to an integer
    strtokIndxSerial1 = strtok(NULL, ",");
    sensorHumSerial1 = atoi(strtokIndxSerial1);     // convert this part to an integer


}


//============


void showParsedDataSerial1(int gasSerial1, int tempSerial1, int humSerial1) {
    Serial.print("Gasconc.: ");
    Serial.println(gasSerial1);
    Serial.print("Temp. : ");
    Serial.println(tempSerial1);
    Serial.print("Humid. : ");
    Serial.println(humSerial1);
    if (gasSerial1 > 10000 || tempSerial1 > 1000 || humSerial1 > 1000) { // in the case received string was wrongly read (mixed up)
      Serial.println("Stop exexcution");
      stop();
    }


}


void showNewDataSerial1() {
        Serial.print("This just in ... ");
        Serial.println(receivedCharsSerial1);
}


void ReadSPECSensorSerial1(int &gasSerial1, int &tempSerial1, int &humSerial1, HardwareSerial& gasPort) {
  gasPort.print('\r');  // print to gasport to get single measurement string 
  recvWithEndMarkerSerial1(gasPort);
  //recvWithStartEndMarkers(gasPort);
  if (newDataSerial1 == true) {
        strcpy(tempCharsSerial1, receivedCharsSerial1);
            // this temporary copy is necessary to protect the original data
            // because strtok() used in parseData() replaces the commas with \0
        showNewDataSerial1();    
        parseDataSerial1(gasSerial1, tempSerial1, humSerial1);
        showParsedDataSerial1(gasSerial1, tempSerial1, humSerial1);
        newDataSerial1 = false;
  }
}


// Functions for CO serial read
void recvWithEndMarkerSerial2(HardwareSerial& gasPort) {
    static byte ndxSerial2 = 0;
    char endMarkerSerial2 = '\n';
    char rcSerial2;
   
    while (gasPort.available() > 0 && newDataSerial2 == false) {
        rcSerial2 = gasPort.read();


        if (rcSerial2 != endMarkerSerial2) {
            receivedCharsSerial2[ndxSerial2] = rcSerial2;
            ndxSerial2++;
            if (ndxSerial2 >= numCharsSerial2) {
                ndxSerial2 = numCharsSerial2 - 1;
            }
        }
        else {
            receivedCharsSerial2[ndxSerial2] = '\0'; // terminate the string
            ndxSerial2 = 0;
            newDataSerial2 = true;
            Serial.println("New data on Serial2 is collected ...");
        }
    }
}






//============


void parseDataSerial2(int &sensorGasSerial2, int &sensorTempSerial2, int &sensorHumSerial2) {      // split the data into its parts


    char * strtokIndxSerial2; // this is used by strtok() as an index
    
    strtokIndxSerial2 = strtok(tempCharsSerial2,",");      // get the first part - the string // http://www.c-howto.de/tutorial/strings-zeichenketten/string-funktionen/string-zerteilen/
    strtokIndxSerial2 = strtok(NULL, ","); // commented out, because of using Serialnumber of Sensor as startMarker
                                      // uncomment, when using recvWithEndMarker() instead of recvWithStartEndMarker()
    sensorGasSerial2 = atoi(strtokIndxSerial2);     // convert this part to an integer
    strtokIndxSerial2 = strtok(NULL, ",");
    sensorTempSerial2 = atoi(strtokIndxSerial2);     // convert this part to an integer
    strtokIndxSerial2 = strtok(NULL, ",");
    sensorHumSerial2 = atoi(strtokIndxSerial2);     // convert this part to an integer


}


//============


void showParsedDataSerial2(int gasSerial2, int tempSerial2, int humSerial2) {
    Serial.print("Gasconc.: ");
    Serial.println(gasSerial2);
    Serial.print("Temp. : ");
    Serial.println(tempSerial2);
    Serial.print("Humid. : ");
    Serial.println(humSerial2);
    if (gasSerial2 > 10000 || tempSerial2 > 1000 || humSerial2 > 1000) { // in the case received string was wrongly read (mixed up)
      Serial.println("Stop exexcution");
      stop();
    }


}


void showNewDataSerial2() {
        Serial.print("This just in ... ");
        Serial.println(receivedCharsSerial2);
}


void ReadSPECSensorSerial2(int &gasSerial2, int &tempSerial2, int &humSerial2, HardwareSerial& gasPort) {
  gasPort.print('\r');  // print to gasport to get single measurement string 
  recvWithEndMarkerSerial2(gasPort);
  //recvWithStartEndMarkers(gasPort);
  if (newDataSerial2 == true) {
        strcpy(tempCharsSerial2, receivedCharsSerial2);
            // this temporary copy is necessary to protect the original data
            // because strtok() used in parseData() replaces the commas with \0
        showNewDataSerial2();    
        parseDataSerial2(gasSerial2, tempSerial2, humSerial2);
        showParsedDataSerial2(gasSerial2, tempSerial2, humSerial2);
        newDataSerial2 = false;
  }
}




//------------------------------------------------
// additional functions
void flush_serial(HardwareSerial& serialPort) {
  // Do we have data in the serial input buffer?
  // If so, flush it
Serial.println("Flush the serial input buffer!");  
while (serialPort.available() > 0) {
  serialPort.read();
  Serial.println("in while loop!");  
}
Serial.println("Serial flushed! ");
}


// comment stop() out everywhere
// only for catching garbage strings and therefore testing purposes
void stop()
{
 while(1);
}

If you're interested you can try the attached.

I assume you know the serial numbers of the sensors. I looked at your outputs and used those serial numbers there; if they're wrong just update them in the struct initializations.

sketch_nov07c.ino (12.7 KB)

Okay, I've written another possible solution, which does seem to work for the SPEC NO2 and O3 sensors, but not with the CO Sensors. At this point, I think there is something wrong with the Firmware of the CO sensors of SPEC Sensors, where sometimes no newline character gets added by the CO SPEC Sensor to their measurement strings. Therefore, I get sometimes this for a SPEC Sensor:

This just in ... 020419011539, 7055, 21, 63, 13350, 25652, 36562, 00, 02, 01, 21020419011539, 7055, 21, 63, 13350, 25652, 36554, 00, 02, 01, 22

where 2 measurement strings get immediately pasted together and I've seen this behaviour only with my 2 CO SPEC Sensors I own and which I've ordered from Digikey at the same time. Maybe there are both from the same batch. I have 2 fresh ones and I'm looking forward to check, if my suspicion is true.

Is there a function, I could use to count the length of the characters in my buffer array? This way, I could check, if the "paste" issue ever happens to my other 2 spec sensors (NO2 and O3) and make the Arduino stop with an endless while loop.

The way I've programmed my code is, that only a new measurement string gets send with printing '\r', when no new data was received and when the last received data was parsed:

17:31:41.714 -> Read O3 Sensor:
17:31:41.714 -> New data on Serial3 is collected ...
17:31:41.748 -> This just in ... 061318011342, 30, 21, 64, 32664, 25586, 36722, 00, 02, 11, 04

17:31:41.850 -> Gasconc.: 30
17:31:41.850 -> Temp. : 21
17:31:41.850 -> Humid. : 64
17:31:41.884 -> -----------------------------------
17:31:41.918 -> Read CO Sensor:
17:31:41.952 -> 
17:31:41.952 -> Read NO2 Sensor:
17:31:41.952 -> 
17:31:41.952 -> Read O3 Sensor:
17:31:41.986 -> -----------------------------------
17:31:42.020 -> Read CO Sensor:
17:31:42.020 -> 
17:31:42.020 -> Read NO2 Sensor:
17:31:42.054 -> 
17:31:42.054 -> Read O3 Sensor:
17:31:42.088 -> -----------------------------------
17:31:42.123 -> Read CO Sensor:
17:31:42.123 -> 
17:31:42.123 -> Read NO2 Sensor:
17:31:42.159 -> 
17:31:42.159 -> Read O3 Sensor:
17:31:42.159 -> -----------------------------------
17:31:42.227 -> Read CO Sensor:
17:31:42.227 -> New data on Serial1 is collected ...
17:31:42.261 -> This just in ... 020419011539, 6859, 21, 63, 13342, 25656, 36626, 00, 02, 07, 23

17:31:42.362 -> Gasconc.: 6859
17:31:42.362 -> Temp. : 21
17:31:42.397 -> Humid. : 63
17:31:42.397 -> 
17:31:42.397 -> Read NO2 Sensor:
17:31:42.431 -> New data on Serial2 is collected ...
17:31:42.464 -> This just in ... 091318030321, 8, 21, 63, 32686, 25552, 36550, 00, 02, 06, 38

17:31:42.531 -> Gasconc.: 8
17:31:42.565 -> Temp. : 21
17:31:42.565 -> Humid. : 63

Thank you so much, Blackfin. I will try your code out tomorrow.

Blackfin:
If you're interested you can try the attached.

I assume you know the serial numbers of the sensors. I looked at your outputs and used those serial numbers there; if they're wrong just update them in the struct initializations.

ReadSpec_C_string.ino (10.7 KB)

Here is a simplified sketch, assuming the parsing is the same for each Serial. With some test data.
It uses SafeStrings which are much safer and more robust then c-strings and provide the functionality of Arduino Strings without the memory fragmentation or the odd errors. See the detailed tutorial here. The SafeStringStream is a small class that wraps a SafeString as a Stream for supplying test data.

#include "SafeString.h"  // download from Arduino Library manager
#include "SafeStringStream.h" // download https://www.forward.com.au/pfod/ArduinoProgramming/SafeString/SafeStringStream.html

const byte numCharsSerial = 102; //define size of receive buffer same for all 3

char COdata[] = "061318011256, 7, 21, 50, 32682, 25500, 29842, 00, 00, 31, 07\n";
char NO2data[] = "061318011256, 7, 21, 50, 32682, 25500, 29842, 00, 00, 31, 07\n";
char O3data[] = "061318011256, 7, 21, 50, 32682, 25500, 29842, 00, 00, 31, 07\n";

char coEndMarker[] = "\n";
char no2EndMarker[] = "\n";
char o3EndMarker[] = "\n";

//HardwareSerial& coPort = Serial1;
//HardwareSerial& no2Port = Serial2;
//HardwareSerial& o3Port = Serial3;
cSF(sfcoPortData, numCharsSerial);
SafeStringStream coPort(sfcoPortData);
cSF(sfno2PortData, numCharsSerial);
SafeStringStream no2Port(sfno2PortData);
cSF(sfo3PortData, numCharsSerial);
SafeStringStream o3Port(sfo3PortData);

cSF(coDataReceived, numCharsSerial);
cSF(coDataLeading, 30);
bool coPortParsed = true; // force initial \r

cSF(no2DataReceived, numCharsSerial);
cSF(no2DataLeading, 30);
bool no2PortParsed = true;

cSF(o3DataReceived, numCharsSerial);
cSF(o3DataLeading, 30);
bool o3PortParsed = true;

// use long here so can just pass them directly ot the toLong() method
long no2;
long no2temp;
long no2hum;

long co;
long cotemp;
long cohum;

long o3;
long o3temp;
long o3hum;
//============

void setup() {
  Serial.begin(9600);
  for (int i = 10; i > 0; i--) {
    Serial.print(' '); Serial.print(i);
    delay(500);
  }
  Serial.println();
  //    coPort.begin(9600);
  //    no2Port.begin(9600);
  //    o3Port.begin(9600);
  Serial.println(F("Demo for collecting and parsing measurement string from a SPEC sensor."));
  Serial.println(F("Based on the SafeString V2 library."));
  Serial.println();
  flush_serial(coPort);
  flush_serial(no2Port);
  flush_serial(o3Port);
}

//============

void loop() {

  if (coPortParsed) { // only force a measurement, when data was parsed successfully
    coPort.print('\r');  // print to gasport to get single measurement string
    sfcoPortData = COdata; // load stream with next set of test data
    coPortParsed = false;
  }
  // etc for the other 2 serials

  if (recvWithEndMarker(coPort, coDataReceived, coEndMarker)) {  // parse it
    Serial.println(F("Read CO Sensor:")); Serial.print(coDataReceived);  Serial.println("");
    parseData(co, cotemp, cohum, coDataReceived, coDataLeading);
    showParsedData("CO", co, cotemp, cohum);
    coDataReceived.clear();
    coPortParsed = true;
  }

  // etc for the other 2 serials

  Serial.println(F("-----------------------------------"));
}

//============

// Functions for CO serial read returns true if have read \n
bool recvWithEndMarker(Stream& input, SafeString& dataRead, const char* endMarker) {
  return dataRead.readUntil(input, endMarker); // returns true when a char from endMarker c-string is read, delimiter is returned
}

//============

// This parser does not change the data
void parseData(long &sensorGas, long &sensorTemp, long &sensorHum, SafeString &data, SafeString &leadingChars) { // split the data into its parts
  size_t nextIdx = 0; // this is used by stoken() as an index
  cSF(numToken, 20);
  nextIdx = data.stoken(leadingChars, nextIdx, ","); // get the first part - the string
  nextIdx++; // step over ,
  nextIdx = data.stoken(numToken, nextIdx, ","); // get the first part - the string
  numToken.toLong(sensorGas); // returns false if invalid num
  nextIdx++; // step over ,
  nextIdx = data.stoken(numToken, nextIdx, ","); // get the first part - the string
  numToken.toLong(sensorTemp); // returns false if invalid num
  nextIdx++; // step over ,
  nextIdx = data.stoken(numToken, nextIdx, ","); // get the first part - the string
  numToken.toLong(sensorHum); // returns false if invalid num
}

//============
void showParsedData(const char *title, int gas, int temp, int hum) {
  Serial.print(title);
  Serial.print(F(" Gasconc.: "));
  Serial.println(gas);
  Serial.print(F("Temp. : "));
  Serial.println(temp);
  Serial.print(F("Humid. : "));
  Serial.println(hum);
}

//------------------------------------------------
// additional functions
void flush_serial(Stream& serialPort) {
  // Do we have data in the serial input buffer?
  // If so, flush it
  Serial.println(F("Flush the serial input buffer!"));
  while (serialPort.available() > 0) {
    serialPort.read();
    Serial.println(F("in while loop!"));
  }
  Serial.println(F("Serial flushed! "));
}

Things have progressed since then. V2.0.8 of my SafeString library (available from the Arduino Library manager when update goes through) now includes BufferedOutput and readUntilToken() for non-blocking Serial Text I/O which is what you need here.
see the tutorial Arduino Serial I/O for the Real World for all the details

A complete solution for your application with error checking (with optional test input data) follows

// comment this line out to use real serial1,2,3 input data
#define TEST_DATA

#include "SafeString.h"  // download from Arduino Library manager
#include "SafeStringStream.h" // included in V2.0.7 of SafeString
#include "BufferedOutput.h" // included in V2.0.7 of SafeString
#include "loopTimer.h" // install loopTimer from zip file 
///  from https://www.forward.com.au/pfod/ArduinoProgramming/RealTimeArduino/index.html

createBufferedOutput(output, 80, DROP_UNTIL_EMPTY);
char delimiters[] = "\n"; // same for all three inputs

// to handle inputs
const size_t maxMsgLength = 102; // length of largest command to be recognized, can handle longer input but will not tokenize it.
createSafeString(coInput, maxMsgLength + 1); //  to read input cmd, large enough to hold longest cmd + leading and trailing delimiters
createSafeString(coDataReceived, maxMsgLength + 1); // for parsing, capacity should be >= input
// set to true to skip input until the first \n
bool coSkipToDelimiter = false; // bool variable to hold the skipToDelimiter state across calls to readUntilToken()

createSafeString(no2Input, maxMsgLength + 1); //  to read input cmd, large enough to hold longest cmd + leading and trailing delimiters
createSafeString(no2DataReceived, maxMsgLength + 1); // for parsing, capacity should be >= input
// set to true to skip input until the first \n
bool no2SkipToDelimiter = false; // bool variable to hold the skipToDelimiter state across calls to readUntilToken()

createSafeString(o3Input, maxMsgLength + 1); //  to read input cmd, large enough to hold longest cmd + leading and trailing delimiters
createSafeString(o3DataReceived, maxMsgLength + 1); // for parsing, capacity should be >= input
// set to true to skip input until the first \n
bool o3SkipToDelimiter = false; // bool variable to hold the skipToDelimiter state across calls to readUntilToken()

#ifdef TEST_DATA // replace hardware serial with SafeStringStreams at 9600
// two lines of data for each input
// first one is a partial line be part way through
// if you set ..SkipToDelimiter true it will be skipped on startup
// otherwise it will be rejected as bad data (missing fields)
char COdata[] =  ", 7, 21, 50, 32682, 25500, 29842, 00, 00, 31, 07\n"
                 "061318011256, 7, 21, 50, 32682, 25500, 29842, 00, 00, 31, 07\n";
char NO2data[] = "1, 50, 32682, 25500, 29842, 00, 00, 31, 07\n"
                 "061318011256, 6, 22, 55, 32682, 25500, 29842, 00, 00, 31, 07\n";
char O3data[] =  "32682, 25500, 29842, 00, 00, 31, 07\n"
                 "061318011256, 5, 20, 53, 32682, 25500, 29842, 00, 00, 31, 07\n";
cSFP(sfcoPortData, COdata); // wrap the data
cSF(coRxBuf, 64); // simulate Mega Rx buffer size
SafeStringStream coPort(sfcoPortData, coRxBuf); // put it in a SafeStringStream
cSFP(sfno2PortData, NO2data);
cSF(no2RxBuf, 64); // simulate Mega Rx buffer size
SafeStringStream no2Port(sfno2PortData, no2RxBuf);
cSFP(sfo3PortData, O3data);
cSF(o3RxBuf, 64); // simulate Mega Rx buffer size
SafeStringStream o3Port(sfo3PortData, o3RxBuf);

#else  // use real Serial inputs
HardwareSerial& coPort = Serial1;
HardwareSerial& no2Port = Serial2;
HardwareSerial& o3Port = Serial3;
#endif

int no2; int no2temp; int no2hum;
int co; int cotemp; int cohum;
int o3; int o3temp; int o3hum;
//============

void setup() {
  Serial.begin(115200);
  for (int i = 10; i > 0; i--) {
    Serial.print(' '); Serial.print(i);
    delay(500);
  }
  Serial.println();
  Serial.println(F("Demo for collecting and parsing measurement string from a SPEC sensor."));
  Serial.println(F("Based on the SafeString V2.0.8+ library."));
  Serial.println();
  output.connect(Serial);
  SafeString::setOutput(Serial); // for SafeString Error msg. NOTE: sent to Serial so will block rest of the loop!!
  coPort.begin(9600);
  no2Port.begin(9600);
  o3Port.begin(9600);
}

//============

void loop() {
  output.nextByteOut(); // output next buffered chars. Need to call this at least once each loop
  loopTimer.check(output);
  if (no2Input.readUntilToken(no2Port, no2DataReceived, delimiters, no2SkipToDelimiter, false)) { // echo on, recycles the input back to the SafeStringStream
    if (parseData(no2, no2temp, no2hum, no2DataReceived)) {
      output.print("  NO2 data : "); output.print(no2DataReceived);
      showParsedData(" >>>>> NO2 ", no2, no2temp, no2hum);
    } else {
      output.print("!!NO2 bad data :"); output.println(no2DataReceived);
    }
  }
  if (o3Input.readUntilToken(o3Port, o3DataReceived, delimiters, o3SkipToDelimiter, false)) { // echo on, recycles the input back to the SafeStringStream
    if (parseData(o3, o3temp, o3hum, o3DataReceived)) {
      output.print("   O3 data : "); output.print(o3DataReceived);
      showParsedData(" >>>>>  O3 ", o3, o3temp, o3hum);
    } else {
      output.print("!!O3 bad data  :"); output.println(o3DataReceived);
    }
  }
  if (coInput.readUntilToken(coPort, coDataReceived, delimiters, coSkipToDelimiter, false)) { // echo on, recycles the input back to the SafeStringStream
    if (parseData(co, cotemp, cohum, coDataReceived)) {
      output.print("   CO data : "); output.print(coDataReceived);
      showParsedData(" >>>>>  CO ", co, cotemp, cohum);
    } else {
      output.print("!!CO bad data  :"); output.println(coDataReceived);
    }
  }
}

//============

// This parser does not change the data,
// returns true if data valid  else false
// if false returned sensorGas or sensorTemp or sensorHum keep their previous values
bool parseData(int &sensorGas, int &sensorTemp, int &sensorHum, SafeString &data) { // split the data into its parts
  int gas, temp, hum; // temp results

  cSF(sfField, 20); // temp SafeString to received fields, max field len is <11;
  char delims[] = ",\n"; // fields delimited by ,
  bool returnEmptyFields = true; // return empty field for ,,
  size_t idx = 0;
  idx = data.stoken(sfField, idx, delims, returnEmptyFields); // get the first part
  // skip first field
  idx = data.stoken(sfField, idx, delims, returnEmptyFields); // get Gas
  if (!sfField.toInt(gas)) {
    return false;
  }
  idx = data.stoken(sfField, idx, delims, returnEmptyFields); // get Gas
  if (!sfField.toInt(temp)) {
    return false;
  }
  idx = data.stoken(sfField, idx, delims, returnEmptyFields); // get Gas
  if (!sfField.toInt(hum)) {
    return false;
  }
  // check the rest of the expected fields exist, there should be 7 non-empty fields
  for (int i = 0; i < 7; i++) {
    idx = data.stoken(sfField, idx, delims, returnEmptyFields); // get Gas
    if (sfField.isEmpty()) {
      return false;
    }
  }
  // finally check ranges
  if (gas > 10000 || temp > 1000 || hum > 1000) { // in the case received string was wrongly read (mixed up)
    return false;
  }
  // else all good updated inputs
  sensorGas = gas;
  sensorTemp = temp;
  sensorHum = hum;
  return true;
}

//============
void showParsedData(const char *title, int gas, int temp, int hum) {
  cSF(msg, 50);
  msg.print(title);
  msg.print(F(" Gasconc:"));   msg.print(gas);
  msg.print(F(" Temp:"));   msg.print(temp);
  msg.print(F(" Humid:"));   msg.print(hum);
  msg.println();
  output.clearSpace(msg.length());
  output.print(msg);
  output.protect(); // prevent other clearSpace() calls from removing parts of this output
}

Sample test output is

Demo for collecting and parsing measurement string from a SPEC sensor.
Based on the SafeString V2.0.8+ library.

!!O3 bad data  :32682, 25500, 29842, 00, 00, 31, 07
!!NO2 bad data :1, 50, 32682, 25500, 29842, 00, 00, 31, 07
!!CO bad data  :, 7, 21, 50, 32682, 25500, 29842, 00, 00, 31, 07
   O3 data : 061318011256, 5, 20, 53, 32682, 25500, 29842, 00, 00, 31, 07 >>>>>  O3  Gasconc:5 Temp:20 Humid:53
  NO2 data : 061318011256, 6, 22, 55, 32682, 25500, 29842, 00, 00, 31, 07 >>>>> NO2  Gasconc:6 Temp:22 Humid:55
   CO data : 061318011256, 7, 21, 50, 32682, 25500, 29842, 00, 00, 31, 07 >>>>>  CO  Gasconc:7 Temp:21 Humid:50
loop uS Latency
 5sec max:14060 avg:137
 sofar max:14060 avg:137 max - prt:1984

Thank you so much! I already played with your newer library version and made your older example work with the new library. Your new example is also a great help!

I had to make some small changes, also based on the older example you posted. I have to print \r to the Serial Port of the gas sensor, so the gas sensor sends a measurement string to the Arduino.

The changes to your code (I added a flag for data being actually parsed, so the new print to the gas port is gated behind this flag):

if (coPortParsed) { // only force a measurement, when data was parsed successfully
    coPort.print('\r');  // print to gasport to get single measurement string
    coPortParsed = false;
  }
// etc for the other 2 serials
 
  if (no2Input.readUntilToken(no2Port, no2DataReceived, delimiters, no2SkipToDelimiter, false)) { // echo on, recycles the input back to the SafeStringStream
    if (parseData(no2, no2temp, no2hum, no2DataReceived)) {
      output.print("  NO2 data : "); output.print(no2DataReceived);
      showParsedData(" >>>>> NO2 ", no2, no2temp, no2hum);
      no2PortParsed = true;
    } else {
      output.print("!!NO2 bad data :"); output.println(no2DataReceived);
    }
  }
// etc for the other 2 serials

The output looks like the output in your example in your last post most of the time:

21:01:05.863 -> loop uS Latency

21:01:05.896 ->  5sec max:2436 avg:37

21:01:05.896 ->  sofar max:4232 avg:37 max - prt:1448

21:01:06.062 ->    CO data : 012919012048, 10388, 22, 54, 13455, 25849, 31554, 00, 03, 19, 51  >>>>>  CO  Gasconc:10388 Temp:22 Humid:54

21:01:06.725 ->   NO2 data : 061318011256, 20, 22, 50, 32673, 25839, 29790, 00, 03, 38, 26  >>>>> NO2  Gasconc:20 Temp:22 Humid:50

21:01:07.057 ->    CO data : 012919012048, 10388, 22, 54, 13455, 25849, 31546, 00, 03, 19, 52  >>>>>  CO  Gasconc:10388 Temp:22 Humid:54

21:01:07.720 ->   NO2 data : 061318011256, 20, 22, 50, 32673, 25839, 29790, 00, 03, 38, 27  >>>>> NO2  Gasconc:20 Temp:22 Humid:50

21:01:08.052 ->    CO data : 012919012048, 10387, 22, 54, 13455, 25850, 31550, 00, 03, 19, 53  >>>>>  CO  Gasconc:10387 Temp:22 Humid:54

21:01:08.716 ->   NO2 data : 061318011256, 20, 22, 50, 32673, 25839, 29786, 00, 03, 38, 28  >>>>> NO2  Gasconc:20 Temp:22 Humid:50

21:01:09.048 ->    CO data : 012919012048, 10387, 22, 54, 13455, 25850, 31550, 00, 03, 19, 54  >>>>>  CO  Gasconc:10387 Temp:22 Humid:54

21:01:09.712 ->   NO2 data : 061318011256, 20, 22, 50, 32673, 25839, 29786, 00, 03, 38, 29  >>>>> NO2  Gasconc:20 Temp:22 Humid:50

21:01:10.044 ->    CO data : 012919012048, 10387, 22, 54, 13455, 25850, 31550, 00, 03, 19, 55  >>>>>  CO  Gasconc:10387 Temp:22 Humid:54

21:01:10.709 ->   NO2 data : 061318011256, 20, 22, 50, 32673, 25839, 29794, 00, 03, 38, 30  >>>>> NO2  Gasconc:20 Temp:22 Humid:50

but sometimes my output looks like that inbetween, where nothing follows after the ~:

20:59:55.298 ->    CO data : 012919012048, 10365, 22, 54, 13454, 25841, 31594, 00, 03, 18, 44  >>>>>  CO  Gasconc:10365 Temp:22 Humid:54

20:59:55.431 -> ~~

20:59:55.431 ->  >>>>> NO2  Gasconc:22 Temp:22 Humi~~

20:59:55.896 -> loop uS Latency

20:59:55.929 ->  5sec max:2464 avg:37

20:59:55.962 ->  sofar max:2516 avg:37 max - prt:1608

20:59:56.294 ->    CO data : 012919012048, 10365, 22, 54, 13454, 25841, 31594, 00, 03, 18, 45  >>>>>  CO  Gasconc:10365 Temp:22 Humid:54

20:59:56.427 -> ~~

20:59:56.427 ->  >>>>> NO2  Gasconc:20 Temp:22 ~~

20:59:57.290 ->    CO data : 012919012048, 10365, 22, 54, 13454, 25841, 31594, 00, 03, 18, 46  >>>>>  CO  Gasconc:10365 Temp:22 Humid:54

20:59:57.423 -> ~~

20:59:57.423 ->  >>>>> NO2  Gasconc:20 Tem~~

20:59:58.318 ->    CO data : 012919012048, 10365, 22, 54, 13454, 25841, 31594, 00, 03, 18, 47  >>>>>  CO  Gasconc:10365 Temp:22 Humid:54

20:59:58.418 -> ~~

20:59:58.451 ->  >>>>> NO2  Gasconc:2~~

20:59:59.314 ->    CO data : 012919012048, 10365, 22, 54, 13454, 25841, 31586, 00, 03, 18, 48  >>>>>  CO  Gasconc:10365 Temp:22 Humid:54

20:59:59.413 -> ~~

20:59:59.447 ->  >>>>> NO2  Gasco~~

21:00:00.309 ->   NO2 data : 061318011256, 20, 22, 50, 32673, 25832, 29838, 00, 03, 37, 18  >>>>> NO2  Gasconc:20 Temp:22 Humid:50

21:00:00.

I'm not sure, why this happens.

Also, in the case I receive bad data, can I flush the serial with the bad data with one of your library functions and fill it via a new fresh print('\r')? In this case, the variables gas, hum, temp should not be updated and the values in these variables should be kept. I think that's the case anyways? As far as I understood, every time there is a parsing problem, older values in the variables temp, hum and gas will be kept, as far as I understood? Also, when these ~ show up I guess?

Thanks again for spending your time on my problem! I really appreciate the work and time you put into this great library and into these detailed examples for my problem. :slight_smile:

Well the ~~ tell you some chars have been dropped because the BufferedOutput buffer was full and the Serial output was too slow to send the entire message without blocking the rest of the loo() and so missing your sensor readings.
Check out the detailed tutorial on that Serial I/O for the Real World

First thing to try is to increase the size of the extra output buffer to say 200, i.e. change the code to
createBufferedOutput(output, 200, DROP_UNTIL_EMPTY);

Check that the showParsedData is calling clearSpace() and protect(), see the code I posted

  output.clearSpace(msg.length());
  output.print(msg);
  output.protect(); // prevent other clearSpace() calls from removing parts of this output

But the bottom line is you are trying to output more data to the Arduino monitor than the Serial baud rate can cope with without blocking the rest of your code. BufferedOutput prevents the blocking by dropping some output. Using clearSpace() you can ensure that message will make it into the output buffer. Using protect() for very important messages stops another clearSpace() from removing that msg.
A larger buffer and using those methods should allow you to see the results while perhaps dropping some of the information output

Another important point

loopTimer.check(output);

takes up about 80 bytes of the output buffer every 5secs.
Comment out that line, as well, and try again.

Thank you for all the advise! Everything seems to work perfectly now. I've changed the size of the buffer and commented the code with looptimer.check(output). I will rewrite my code based on your library.

At the moment, I'm a bit concerned about my use of Softwareserial though. I use Softwareserial for another device, because all my Hardwareserials are occupied by the gas sensors I'm using at the moment. My Arduino receives through another device a timestamp string using Softwareserial, which I use to update my RTC occasionally. I also send a string of all my averaged measurements to that other device every 3 minutes using Softwareserial, so the other device sends the data to my database. It basically is connected to the internet, which is not possible with my Mega itself.

For receiving from the other device I'm using code based on example 5 of the serial input basics tutorial: Serial Input Basics - updated - #3 by Robin2 - Introductory Tutorials - Arduino Forum

I hope I don't have to change here anything, because Serial.read() and Serial.available() are non-blocking as far as I know.

Regarding the second part, I'm printing a json-formatted string, which looks kinda like that (In reality I'm sending approx. 34 variables to the Transmission device):

void SendDataModem(float PartMat10, int yyyy, byte mon, byte dd, byte hh, byte mm) {
  SWPort.print(F("{\"Number\":")); SWPort.print(Number); SWPort.print(F(","));
  SwPort.print(F("\"Y\":")); SWPort.print(yyyy); SWPort.print(F(","));
  SWPort.print(F("\"M\":")); SWPort.print(mon); SWPort.print(F(","));
  SWPort.print(F("\"D\":")); SWPort.print(dd); SWPort.print(F(","));
  SWPort.print(F("\"Hr\":")); SWPort.print(hh); SWPort.print(F(","));
  SWPort.print(F("\"Mi\":")); SWPort.print(mm); SWPort.print(F(","));
  SWPort.print(F("\"P10\":")); SWPort.print(PartMat10); SWPort.println(F("}"));

  sending_measurement = false;  // sending data is done
}

As far as I've understood from your tutorial, you always should try to avoid the standard methods from serial. I will change my Serial.print() statements in my code using your method shown in the function showParsedData(). I'm a bit concerned about my current way of using Softwareserial. I'm scared, that I hurt the framework, you just showed me via your library through Softwareserial. In the end, the loop section should only be slower, but using Softwareserial shouldnt hurt receiving data from the gas sensors, or am I wrong?

Edit:
For my debug messages, I would use this section: https://www.forward.com.au/pfod/ArduinoProgramming/SafeString/index.html#debug

but in your example I've also read, that too many output.print() can make my debug output a mess (Section: Help my output is a mess).

Occasionally, I have single Serial.print() functions (Code below). Writing a function (like showParsedData()) for every single line wouldn't be the wisest decision I guess? Making the amount of bytes in

createBufferedOutput(output, 200, DROP_UNTIL_EMPTY)

just larger than 200 bytes would probably already help, so I can use output.print() for my debugging messages inside my loop section of the code?

 if (first_sensor_reading == false) { // after first sensor read turn backlight off
      Serial.println(F("First averaging, sensor off"));
      lcd.setBacklight(0);
    }

    Serial.print(F("Index_avg: ")); Serial.println(idx_avg);
    Serial.print(F("Index_time: ")); Serial.println(idx_time);
    idx_time++;
    if (idx_time == numMeasTime) {
      idx_time = 0;
      if ( now.minute () == avgTime[idx_avg] ) {
        last_measurement_min = true;
        Serial.println(F("Last minute of interval for averaging reached"));
        Serial.println(F(""));
      }
      Serial.print(F("Index_time set back to Zero: ")); Serial.println(idx_time);
    }
  }

Hi,
A couple of points. For Mega, the recommended software serial appears to be AltSoftSerial library.

You can mix BufferedOutput on one output with non-buffered outputs no problem

No problem using softserial apart from that it will block and you will miss some sensor reading.
On the other hand you can buffer its output as well

For your 3min json output you can createBufferedOutput(jsonOut, 120, BLOCK_IF_FULL);
Adjust the 120 to be large enough for your whole message.
Then in the setup connect it to your softserial
jsonOut.connect( softserial )
and add in the loop() jsonOut.nextByteOut();

For reading timestamps, yes using available() and read() works, but you have to be careful to avoid coding errors, buffer overflow, missing terminating nulls. Once you have the data in a char[] you can wrap it in a SafeString to safely split it into parts. See the SafeString library example sketches and the SafeString tutorial
OR you can put a '\n' (newline) on the end of you timstamp and use readUntilToken again to collect the line for processing. Either way will work. Using readUntilToken avoids the low level byte by byte handling code.

As noted in the tutoral, if you call Serial directly it will 1) by pass the buffered output and inject chars in the middle of some other message and 2) will block your loop. Not recommended, so use output.print() instead.
You can change the bufferedOutput to BLOCK_IF_FULL to get ALL the output but it will block you loop. OR you can use clearSpace() and protect() for special messages OR you can call output.flush() first to completely clear the buffered output and then output.print() and then output.flush()
Just changing to BLOCK_IF_FULL is often the easiest. You get a bigger output buffer but otherwise is works just like Serial.print()

It would probably be a good idea to just code up the timestamp / json code in a separate sketch and add a loopTimer to see what is happening and get that code running correctly before integrating it into the main sketch.

You can post that sketch here if you need help.