Reading sensor using hardware serial

Hello,

I'm trying to read the COZIR Ambient sensor Using the rx and tx ports on the arduino. I'm having a lot of trouble understanding what I'm supposed to do because I can't find a decent explanation or working example that reads values from the sensor anywhere. I've been trying hours and the best I got out of it are just some random characters. Can anyone explain to me how to get a float Co2, humidity and temperature value from the sensor? I'm sorry I'm asking so much, but I've wasted hours on this and with my limited knowledge and the limited information I just can't get it to work. I can't find any working libraries either.

The 3.3V and GND are connected and the RX and TX are connected to the TX and RX of the arduino uno, respectively.

I'll try to give as much info as possible (see attachments):
Cozir Manual-GSS-Sensors.pdf - I think chapter two is the most important.
COZIR Datasheets(1).pdf - I dont think this is very relevant but I'm not sure so here it is.

I would like the output like this : H 00345 T 01195 Z 00651\r\n, (2.3.4 in the manual). I don't even know how to remove the extra letters and zeros.

Again, I know I'm asking a lot and I am sorry for that, please know I will be very thankful to the people who help me out with this.

COZIR Datasheets (1).pdf (162 KB)

Cozir Manual-GSS-Sensors.pdf (760 KB)

You can't have more than 1 device connected to a serial port. It is best to leave the hardware serial port connected to serial monitor for program upload, debugging and program execution and variable monitoring. Use a software serial port for the sensor.

The serial input basics tutorial will show how to read the data into the Uno as a string. Then you can use string processing functions to parse the data.

Here is an example of how to parse the data string from your OP. It uses the strtok() function to separate the values and the atoi() function to convert the ASCII text values to int data type (numbers).

char inputString[] = "H 00345 T 01195 Z 00651";
char *strings[8];
char *ptr = NULL;

void setup()
{
   Serial.begin(115200);
   byte index = 0;
   ptr = strtok(inputString, " ");
   while (ptr != NULL)
   {
      strings[index] = ptr;
      index++;
      ptr = strtok(NULL, " ");
   }
   //Serial.println(index);
   //for(int n = 0; n < index; n++)
   //{
   // Serial.println(strings[n]);
   //}
   Serial.print("humidity = ");
   Serial.print(atoi(strings[1]));
   Serial.print("  temperature = ");
   Serial.print(atoi(strings[3]));
   Serial.print("  CO2 = ");
   Serial.println(atoi(strings[5]));
}

void loop()
{

}

sappigesoes:
...
The 3.3V and GND are connected and the RX and TX are connected to the TX and RX of the arduino uno, respectively.
...
I would like the output like this : H 00345 T 01195 Z 00651\r\n, (2.3.4 in the manual). I don't even know how to remove the extra letters and zeros.
....

As per the example in section 2.3.4 of the manual, given that you have connected everything, did you try to using the serial monitor to sent the command:

M 4164

(or M4164 not clear to me is space is required or not)
"\r\n" will be send you automatically provided you've set the monitor to 'Both NL & CR"
Did you manage to get a response?

You can't have more than 1 device connected to a serial port. It is best to leave the hardware serial port connected to serial monitor for program upload, debugging and program execution and variable monitoring. Use a software serial port for the sensor.

The serial input basics tutorial will show how to read the data into the Uno as a string. Then you can use string processing functions to parse the data.

Ok, I made a SoftwareSerial, the RX and TX are now connected to pin 8 and 9. I should look at example 3 in the tutorial

groundFungus:
You can't have more than 1 device connected to a serial port. It is best to leave the hardware serial port connected to serial monitor for program upload, debugging and program execution and variable monitoring. Use a software serial port for the sensor.

The serial input basics tutorial will show how to read the data into the Uno as a string. Then you can use string processing functions to parse the data.

Ok, I made a SoftwareSerial, the RX and TX are now connected to pin 8 and 9. I should look at example 3 in the tutorial right? Do I replace ever Serial with the softwareserial I created? Or just the serial.read?

sherzaad:
As per the example in section 2.3.4 of the manual, given that you have connected everything, did you try to using the serial monitor to sent the command:

M 4164

(or M4164 not clear to me is space is required or not)
"\r\n" will be send you automatically provided you've set the monitor to 'Both NL & CR"
Did you manage to get a response?

No response unfortunately. i can't imagine I wired it wrong. Maybe something else is wrong? (My code was just Serial.begin(9600) in the setup, and i typed the command in the send box in the serial monitor). The rx, tx pins on the sensor are connected to pin 1, 0 on the arduino respectively.

Here is an example to illustrate what I mean by using a software serial port for the Uno to talk to the sensor and hardware (USB) for you to talk to the Uno. Whatever you enter in the serial monitor is passed to the sensor. So, for instance, if "M 4164" is the command, type that into serial monitor and send (without quotes). When the sensor sends data back via software serial port, the data will be displayed and parsed according to the earlier posted code. Connect the sensor TX to the Uno pin 8 and the sensor RX to Uno pin 9.

#include <SoftwareSerial.h>

SoftwareSerial ss(8, 9);

char inputString[] = "H 00345 T 01195 Z 00651";

char *strings[8];
char *ptr = NULL;

// software serial port
const byte numChars_ss = 32;
char receivedChars_ss[numChars_ss];
boolean newData_ss = false;

// hardware serial port
const byte numChars = 32;
char receivedChars[numChars];   // an array to store the received data
boolean newData = false;

void setup()
{
   Serial.begin(9600);
   Serial.println("<Arduino is ready>");
   ss.begin(9600);
}

void loop()
{
   // watches the hardware serial port (setial monitor)
   // enter "M 4164" to enter to request data from sensor
   recvWithEndMarker();
   // show data from setial monitor and send it out to sensor
   showNewData();

   // watch the software serial port (sensor)
   recvWithEndMarker_ss();
   // if the sensor has sent data parse and display it.
   if (newData_ss == true)
   {
      showNewData_ss();
      parseData_ss();
   }
}

//get data from software serial (sensor)
void recvWithEndMarker_ss()
{
   static byte ndx = 0;
   char endMarker = '\n';
   char rc;

   while (ss.available() > 0 && newData_ss == false)
   {
      rc = ss.read();
      if (rc == '\r')
      {
         return;  // ignore carriage return
      }
      if (rc != endMarker)
      {
         receivedChars_ss[ndx] = rc;
         ndx++;
         if (ndx >= numChars_ss)
         {
            ndx = numChars_ss - 1;
         }
      }
      else
      {
         receivedChars_ss[ndx] = '\0'; // terminate the string
         ndx = 0;
         newData_ss = true;
      }
   }
}

void showNewData_ss()
{
   Serial.print("This just in ... ");
   Serial.println(receivedChars_ss);
   //newData = false;
}
//parse sensor data
void parseData_ss()
{
   byte index = 0;
   ptr = strtok(receivedChars_ss, " ");
   while (ptr != NULL)
   {
      strings[index] = ptr;
      index++;
      ptr = strtok(NULL, " ");
   }
   //Serial.println(index);
   //for(int n = 0; n < index; n++)
   //{
   // Serial.println(strings[n]);
   //}
   Serial.print("humidity = ");
   Serial.print(atoi(strings[1]));
   Serial.print("  temperature = ");
   Serial.print(atoi(strings[3]));
   Serial.print("  CO2 = ");
   Serial.println(atoi(strings[5]));
   newData_ss = false;
}

//get data from hardware serial (serial monitor)
void recvWithEndMarker()
{
   static byte ndx = 0;
   char endMarker = '\n';
   char rc;

   while (Serial.available() > 0 && newData == false)
   {
      rc = Serial.read();
      if (rc == '\r')
      {
         return;  // ignore carriage return
      }
      if (rc != endMarker)
      {
         receivedChars[ndx] = rc;
         ndx++;
         if (ndx >= numChars)
         {
            ndx = numChars - 1;
         }
      }
      else
      {
         receivedChars[ndx] = '\0'; // terminate the string
         ndx = 0;
         newData = true;
      }
   }
}

//echo data from serial monitor and send it out to sensor
void showNewData()
{
   if (newData == true)
   {
      Serial.print("This just in ... ");
      Serial.println(receivedChars);
      ss.println(receivedChars);
      Serial.println(receivedChars);
      newData = false;
   }
}

groundFungus:
Here is an example to illustrate what I mean by using a software serial port for the Uno to talk to the sensor and hardware (USB) for you to talk to the Uno. Whatever you enter in the serial monitor is passed to the sensor. So, for instance, if "M 4164" is the command, type that into serial monitor and send (without quotes). When the sensor sends data back via software serial port, the data will be displayed and parsed according to the earlier posted code. Connect the sensor TX to the Uno pin 8 and the sensor RX to Uno pin 9.

#include <SoftwareSerial.h>

SoftwareSerial ss(8, 9);

char inputString[] = "H 00345 T 01195 Z 00651";

char *strings[8];
char *ptr = NULL;

// software serial port
const byte numChars_ss = 32;
char receivedChars_ss[numChars_ss];
boolean newData_ss = false;

// hardware serial port
const byte numChars = 32;
char receivedChars[numChars];  // an array to store the received data
boolean newData = false;

void setup()
{
  Serial.begin(9600);
  Serial.println("");
  ss.begin(9600);
}

void loop()
{
  // watches the hardware serial port (setial monitor)
  // enter "M 4164" to enter to request data from sensor
  recvWithEndMarker();
  // show data from setial monitor and send it out to sensor
  showNewData();

// watch the software serial port (sensor)
  recvWithEndMarker_ss();
  // if the sensor has sent data parse and display it.
  if (newData_ss == true)
  {
      showNewData_ss();
      parseData_ss();
  }
}

//get data from software serial (sensor)
void recvWithEndMarker_ss()
{
  static byte ndx = 0;
  char endMarker = '\n';
  char rc;

while (ss.available() > 0 && newData_ss == false)
  {
      rc = ss.read();
      if (rc == '\r')
      {
        return;  // ignore carriage return
      }
      if (rc != endMarker)
      {
        receivedChars_ss[ndx] = rc;
        ndx++;
        if (ndx >= numChars_ss)
        {
            ndx = numChars_ss - 1;
        }
      }
      else
      {
        receivedChars_ss[ndx] = '\0'; // terminate the string
        ndx = 0;
        newData_ss = true;
      }
  }
}

void showNewData_ss()
{
  Serial.print("This just in ... ");
  Serial.println(receivedChars_ss);
  //newData = false;
}
//parse sensor data
void parseData_ss()
{
  byte index = 0;
  ptr = strtok(receivedChars_ss, " ");
  while (ptr != NULL)
  {
      strings[index] = ptr;
      index++;
      ptr = strtok(NULL, " ");
  }
  //Serial.println(index);
  //for(int n = 0; n < index; n++)
  //{
  // Serial.println(strings[n]);
  //}
  Serial.print("humidity = ");
  Serial.print(atoi(strings[1]));
  Serial.print("  temperature = ");
  Serial.print(atoi(strings[3]));
  Serial.print("  CO2 = ");
  Serial.println(atoi(strings[5]));
  newData_ss = false;
}

//get data from hardware serial (serial monitor)
void recvWithEndMarker()
{
  static byte ndx = 0;
  char endMarker = '\n';
  char rc;

while (Serial.available() > 0 && newData == false)
  {
      rc = Serial.read();
      if (rc == '\r')
      {
        return;  // ignore carriage return
      }
      if (rc != endMarker)
      {
        receivedChars[ndx] = rc;
        ndx++;
        if (ndx >= numChars)
        {
            ndx = numChars - 1;
        }
      }
      else
      {
        receivedChars[ndx] = '\0'; // terminate the string
        ndx = 0;
        newData = true;
      }
  }
}

//echo data from serial monitor and send it out to sensor
void showNewData()
{
  if (newData == true)
  {
      Serial.print("This just in ... ");
      Serial.println(receivedChars);
      ss.println(receivedChars);
      Serial.println(receivedChars);
      newData = false;
  }
}

After some puzzling I figured out that M 4164 apparently just sets the sensor to output T, H and Z. If I send M 4164 and after that Q/r/n (return latest values according to manual) it works!

14:04:16.836 -> This just in ... Q/r/n
14:04:16.883 -> Q/r/n
14:04:16.883 -> This just in ... H 00590 T 01210 Z 01071
14:04:16.930 -> humidity = 590 temperature = 1210 CO2 = 1071

Is it possible to create a function to get these values automatically every time they are updated (every 0.5 seconds) and have them returned as floats? How would I do that?

Maybe we can use the streaming mode for this.

Thank you for helping me, I really appreciate it. Would've never been able to do this on my own I think.

Added a function to request data every 0.5 seconds (adjustable by changing the sensorInterval variable). The function uses the non-blocking millis() timing as shown in the blink without delay example. Other tutorials for millis() timing are:
Several things at a time.
Beginner's guide to millis().

Changed the atoi() function to the atof() function to return float data types in the parse function.

I put the string "M 4164 Q" in the new function to request data. That may need to be adjusted. The facility to enter data to send to the sensor from serial monitor is retained.

#include <SoftwareSerial.h>

SoftwareSerial ss(8, 9);

char *strings[8];
char *ptr = NULL;

// software serial port
const byte numChars_ss = 32;
char receivedChars_ss[numChars_ss];
boolean newData_ss = false;

// hardware serial port
const byte numChars = 32;
char receivedChars[numChars];   // an array to store the received data
boolean newData = false;

unsigned long sensorInterval = 500;  

void setup()
{
   Serial.begin(9600);
   Serial.println("<Arduino is ready>");
   ss.begin(9600);
}

void loop()
{
   // request data from sensor every sensorInterval milliseconds
   getSensorData();
   // watches the hardware serial port (setial monitor)
   // enter "M 4164 Q" to enter to request data from sensor
   recvWithEndMarker();
   // show data from setial monitor and send it out to sensor
   showNewData();

   // watch the software serial port (sensor)
   recvWithEndMarker_ss();
   // if the sensor has sent data parse and display it.
   if (newData_ss == true)
   {
      showNewData_ss();
      parseData_ss();
   }
}

void getSensorData()
{
   static unsigned long timer = 0;
   unsigned long interval = sensorInterval;
   if (millis() - timer >= interval)
   {
      timer = millis();
      ss.println("M 4164 Q");
   }
}

   //get data from software serial (sensor)
   void recvWithEndMarker_ss()
   {
      static byte ndx = 0;
      char endMarker = '\n';
      char rc;

      while (ss.available() > 0 && newData_ss == false)
      {
         rc = ss.read();
         if (rc == '\r')
         {
            return;  // ignore carriage return
         }
         if (rc != endMarker)
         {
            receivedChars_ss[ndx] = rc;
            ndx++;
            if (ndx >= numChars_ss)
            {
               ndx = numChars_ss - 1;
            }
         }
         else
         {
            receivedChars_ss[ndx] = '\0'; // terminate the string
            ndx = 0;
            newData_ss = true;
         }
      }
   }

   void showNewData_ss()
   {
      Serial.print("This just in ... ");
      Serial.println(receivedChars_ss);
      //newData = false;
   }
   //parse sensor data
   void parseData_ss()
   {
      byte index = 0;
      ptr = strtok(receivedChars_ss, " ");
      while (ptr != NULL)
      {
         strings[index] = ptr;
         index++;
         ptr = strtok(NULL, " ");
      }
      //Serial.println(index);
      //for(int n = 0; n < index; n++)
      //{
      // Serial.println(strings[n]);
      //}
      Serial.print("humidity = ");
      Serial.print(atof(strings[1]));
      Serial.print("  temperature = ");
      Serial.print(atof(strings[3]));
      Serial.print("  CO2 = ");
      Serial.println(atof(strings[5]));
      newData_ss = false;
   }

   //get data from hardware serial (serial monitor)
   void recvWithEndMarker()
   {
      static byte ndx = 0;
      char endMarker = '\n';
      char rc;

      while (Serial.available() > 0 && newData == false)
      {
         rc = Serial.read();
         if (rc == '\r')
         {
            return;  // ignore carriage return
         }
         if (rc != endMarker)
         {
            receivedChars[ndx] = rc;
            ndx++;
            if (ndx >= numChars)
            {
               ndx = numChars - 1;
            }
         }
         else
         {
            receivedChars[ndx] = '\0'; // terminate the string
            ndx = 0;
            newData = true;
         }
      }
   }

   //echo data from serial monitor and send it out to sensor
   void showNewData()
   {
      if (newData == true)
      {
         Serial.print("This just in ... ");
         Serial.println(receivedChars);
         ss.println(receivedChars);
         Serial.println(receivedChars);
         newData = false;
      }
   }

groundFungus:
Added a function to request data every 0.5 seconds (adjustable by changing the sensorInterval variable). The function uses the non-blocking millis() timing as shown in the blink without delay example. Other tutorials for millis() timing are:
Several things at a time.
Beginner's guide to millis().

Changed the atoi() function to the atof() function to return float data types in the parse function.

I put the string "M 4164 Q" in the new function to request data. That may need to be adjusted. The facility to enter data to send to the sensor from serial monitor is retained.

#include <SoftwareSerial.h>

SoftwareSerial ss(8, 9);

char *strings[8];
char *ptr = NULL;

// software serial port
const byte numChars_ss = 32;
char receivedChars_ss[numChars_ss];
boolean newData_ss = false;

// hardware serial port
const byte numChars = 32;
char receivedChars[numChars];  // an array to store the received data
boolean newData = false;

unsigned long sensorInterval = 500;

void setup()
{
  Serial.begin(9600);
  Serial.println("");
  ss.begin(9600);
}

void loop()
{
  // request data from sensor every sensorInterval milliseconds
  getSensorData();
  // watches the hardware serial port (setial monitor)
  // enter "M 4164 Q" to enter to request data from sensor
  recvWithEndMarker();
  // show data from setial monitor and send it out to sensor
  showNewData();

// watch the software serial port (sensor)
  recvWithEndMarker_ss();
  // if the sensor has sent data parse and display it.
  if (newData_ss == true)
  {
      showNewData_ss();
      parseData_ss();
  }
}

void getSensorData()
{
  static unsigned long timer = 0;
  unsigned long interval = sensorInterval;
  if (millis() - timer >= interval)
  {
      timer = millis();
      ss.println("M 4164 Q");
  }
}

//get data from software serial (sensor)
  void recvWithEndMarker_ss()
  {
      static byte ndx = 0;
      char endMarker = '\n';
      char rc;

while (ss.available() > 0 && newData_ss == false)
      {
        rc = ss.read();
        if (rc == '\r')
        {
            return;  // ignore carriage return
        }
        if (rc != endMarker)
        {
            receivedChars_ss[ndx] = rc;
            ndx++;
            if (ndx >= numChars_ss)
            {
              ndx = numChars_ss - 1;
            }
        }
        else
        {
            receivedChars_ss[ndx] = '\0'; // terminate the string
            ndx = 0;
            newData_ss = true;
        }
      }
  }

void showNewData_ss()
  {
      Serial.print("This just in ... ");
      Serial.println(receivedChars_ss);
      //newData = false;
  }
  //parse sensor data
  void parseData_ss()
  {
      byte index = 0;
      ptr = strtok(receivedChars_ss, " ");
      while (ptr != NULL)
      {
        strings[index] = ptr;
        index++;
        ptr = strtok(NULL, " ");
      }
      //Serial.println(index);
      //for(int n = 0; n < index; n++)
      //{
      // Serial.println(strings[n]);
      //}
      Serial.print("humidity = ");
      Serial.print(atof(strings[1]));
      Serial.print("  temperature = ");
      Serial.print(atof(strings[3]));
      Serial.print("  CO2 = ");
      Serial.println(atof(strings[5]));
      newData_ss = false;
  }

//get data from hardware serial (serial monitor)
  void recvWithEndMarker()
  {
      static byte ndx = 0;
      char endMarker = '\n';
      char rc;

while (Serial.available() > 0 && newData == false)
      {
        rc = Serial.read();
        if (rc == '\r')
        {
            return;  // ignore carriage return
        }
        if (rc != endMarker)
        {
            receivedChars[ndx] = rc;
            ndx++;
            if (ndx >= numChars)
            {
              ndx = numChars - 1;
            }
        }
        else
        {
            receivedChars[ndx] = '\0'; // terminate the string
            ndx = 0;
            newData = true;
        }
      }
  }

//echo data from serial monitor and send it out to sensor
  void showNewData()
  {
      if (newData == true)
      {
        Serial.print("This just in ... ");
        Serial.println(receivedChars);
        ss.println(receivedChars);
        Serial.println(receivedChars);
        newData = false;
      }
  }

Thank you very much, it works flawlessly! You really helped me out here!
A bit off topic, but I've ran into a small other problem, which you might know the solution to. If I use the method from the blink without delay, how would I do this without delays?

//Do something
delay(200);
//Do something
delay(200);
//Do something
delay(200);
//Do something
delay(3000);

(beep a piezo twice every 3 seconds)

Study the non-blocking timing links that I sent and give it a try. Post your best effort. If you have trouble I will try to help.

I tried this before and unfortunately it didn't work:

      if (millis() - previousMillis >= 2000) {
        tone(piezo, 500);
      }
      if (millis() - previousMillis >= 2200){
        noTone(piezo);
      }
      if (millis() - previousMillis >= 2400) {
        tone(piezo, 500);
      }
      if (millis() - previousMillis >= 2600){
        previousMillis = millis();
        noTone(piezo);
      }

Just saw I didnt read the severalthings at a time tutorial, will be trying that now.

Based on the severatatime tutorial I changed it to this:

      if (millis() - previousMillis >= 2000) {
        tone(piezo, 500);
        previousMillis  += 2000;
      }
      if (millis() - previousMillis >= 200){
        noTone(piezo);
        previousMillis += 200;
      }
      if (millis() - previousMillis >= 200) {
        tone(piezo, 500);
        previousMillis  += 200;
      }
      if (millis() - previousMillis >= 200){
        noTone(piezo);
        previousMillis  += 200;
      }

But I think it is basically the same thing. I'm a bit stuck so I would appreciate some help now. I'll keep trying though.

This will beep 200 milliseconds, pause 200 milliseconds, beep 200 milliseconds and pause 3000 milliseconds. It is like a state machine. Each state executes and sets the time for the next state.

const byte beepPin = 4;

byte beepMode = 0;
unsigned long beepInterval = 200;
int beepFreq = 1000;

void setup()
{
   Serial.begin(115200);

}

void loop()
{
   beep();
}

void beep()
{
   static unsigned long timer = 0;   
   if (millis() - timer >= beepInterval)
   {
      timer = millis();
      switch(beepMode)
      {
         case 0:
         beepInterval = 200;
         tone(beepPin, beepFreq, 200);
         beepMode = 1;
         break;
         case 1:
         beepInterval = 200;
         noTone(beepPin);
         beepMode = 2;
         break;
         case 2:
         beepInterval = 200;
         tone(beepPin, beepFreq, 200);
         beepMode = 3;
         break;
         case 3:
         beepInterval = 3000;
         noTone(beepPin);
         beepMode = 0;
         break;         
      }
   }
}

Thank you very much, everything works now!