AirGradient Forum

S8 CO2 reading of -1

At least it gives enough time and retries to get a valid reading. What I see in the code is that in case of an invalid reading, there are 10 retries in 10x 50ms = 500ms. The chance that there is a valid reading is more likely.

The -1 readings did disappear.

After setting it back to 50ms and with the best cable, I see again -1 results. As a next test I changed it to 250ms. The next 24hrs will tell if this is a solution.

nice finding @ttielemans! my AirGradient also reports -1 for co2 every now and then. didn’t look into it yet and simply filtered it out in grafana but will give your fix a try :+1:

This night I had a single -1 reading. I just increased the delay from my previous test from 250ms to 500ms. I also changed one of the return codes to -2 to get a better idea where it is going wrong, also when the sensor is running not connected to my arduino ide serial monitor screen.
Another test starting. Since the error is intermittent and sporadic, it is a tricky to analyze.
Still I have the idea that I am trying to solve the symptom, not the cause but it could work for now.

The changes I made in airgradient.cpp

while(!(_SoftSerial_CO2->available())) {
    retry++;
    // keep sending request until we start to get a response
    _SoftSerial_CO2->write(CO2Command, 7);

   //added serial output for analysis and increased delay from 50ms to 500ms
  Serial.print("Retry count ");
  Serial.println(retry);**
  delay(500);
    if (retry > 10) {
  	Serial.println("Error while requesting");
        return -1;
    }
}

int timeout = 0; 
    while (_SoftSerial_CO2->available() < 7) {
    timeout++; 
    if (timeout > 10) {
        while(_SoftSerial_CO2->available())  
          _SoftSerial_CO2->read();
       Serial.println("Error while accessing");
        break;                    
    }
   //increased delay from 50ms to 500ms
    delay(500);
}

for (int i=0; i < 7; i++) {
    int byte = _SoftSerial_CO2->read();
    if (byte == -1) {
        result.success = false;
   //added serial output for analysis and retun value -2 to differenciate and make visible in Grafana where things go wrong
  	Serial.println("Error while decoding");
        return -2;
    }
    CO2Response[i] = byte;
}

@ttielemans please let us know the results. If we can improve it, I’ll update the Arduino library.

I will share the results when I am confident I have a good solution. At this moment I am still testing with the orignal airgradient.cpp library and changes to the “Jeff Geerling” sketch. Testing is slow due to the intermittent occurance.

After quite some testing different improvement options, I now have stable values and no drop-outs anymore. I think the problem occured due to timing issues reading serial data. I have simplified the code and this seems to work fine.

This is the code I am currently using in the function “getCO2_Raw()” in the library airgradient.cpp

int AirGradient::getCO2_Raw(){
  const byte CO2Command[] = {0xFE, 0X44, 0X00, 0X08, 0X02, 0X9F, 0X25};
  byte CO2Response[] = {0,0,0,0,0,0,0};
  
  _SoftSerial_CO2->write(CO2Command, 7);
  delay(100);  //give the sensor a bit of time to respond

  if (_SoftSerial_CO2->available()){
    for (int i=0; i < 7; i++) {
      int byte = _SoftSerial_CO2->read();
      CO2Response[i] = byte;
      if (CO2Response[0] != 254) {
        return -1;  //error code for debugging
      }
    } 
    unsigned long val = CO2Response[3]*256 + CO2Response[4];  
    return val;	
  }	
  else
  {
  return -2; //error code for debugging
  }
}

In my main sketch I loop this function in case I receive an exit code -1 or -2 but in the last 24hrs this did not happen once.

I hope that someone else can have an advantage of this change.

Thank you very much for supporting this.

I committed the code to the library and will test it on my devices for a few days. If it also runs stable for me, I will release an updated Arduino library.

Great work @ttielemans, I’ve also noticed the odd spike to -1 occurring at random times which is annoying to someone who wants perfection in their graphs lol. Further I’m also getting the CO2 spiking down to 515 at random times, so not sure what’s causing that at the moment.

@Znook I had the same occasional 515 (also 514 and 516 readings). What I could see was a left shift of the data package, resulting in a reading of 2*256 + 2 (or 3 or 4) resulting in values 514, 515 or 516. In my analysis I could see that the first byte was not the expected 0xFE but that this was shifted the second place.

I am pretty confident that this is also solved since I check in my code if 0xFE is in the first element of the response array. If this is not the case, I return -1 and do a re-read in the main loop.

 if (hasCO2) {
    int stat = -1; 
    while (stat <=0){
      stat = ag.getCO2_Raw();
     }
    message += "# HELP CO2 value, in ppm3\n";
    message += "# TYPE s8_co2 gauge\n";
    message += prefix;
    message += deviceId;
    message += "_";    
    message += "s8_co2";
    message += idString;
    message += String(stat);
    message += "\n";
  }

I also had some serial monitor checks in my code to see if the function ag.getCO2_Raw()
would get any retries. Until now it did not :grinning:.

1 Like

Thank you all for your contributions. I released an update for the AirGradient Arduino library.

Version 1.4.2 includes the above fix.

Hi @Achim_AirGradient ,

I have tried 1.4.2 version, but now it shows me random -1 value and also values of 65XXX.

Currently i’m testing 1.4.1 if there will be any better results…

That is really strange. I can only say that the -1, which is a return value, does not occur anymore. I have never experienced the 65xxx error.

What you can do, is to change to code in the library AirGradient.cpp and monitor the responses on the serial port. The first byte in the CO2Response array should be 254 decimal or FE hex for a correct read.

int AirGradient::getCO2_Raw(){
  const byte CO2Command[] = {0xFE, 0X44, 0X00, 0X08, 0X02, 0X9F, 0X25};
  byte CO2Response[] = {0,0,0,0,0,0,0};
  
  _SoftSerial_CO2->write(CO2Command, 7);
  delay(100);
  
  if (_SoftSerial_CO2->available()){
	for (int i=0; i < 7; i++) {
      int byte = _SoftSerial_CO2->read();
      CO2Response[i] = byte;
	  if (CO2Response[0] != 254) {
		  Serial.println("Invalid serial read"); //added for debugging
	      Serial.print (CO2Response[i],HEX);     //added for debugging 
	      Serial.print (":");                    //added for debugging
		  return -1;
	  }
		  Serial.print (CO2Response[i],HEX);     //added for debugging
	      Serial.print (":"); 	                 //added for debugging
	} 
    unsigned long val = CO2Response[3]*256 + CO2Response[4];  
    return val;	
  }	
  else
  {
    return -2;
  }
}

@ttielemans

well the values are not 65XXX, but 65432 and the last three digits changes randomly, thats why i wrote XXX at the end, because this value is not constant.

With 1.4.1 it works great for me, no more -1 or other implausible values…

Maybe the CO2 sensor version (HW or FW) has some affect on serial communication?

@Robert_Pogacar Yes, I understood that you meant the last 3 digits changing randomly. I was investigating another sources on github and came accross a function able show the S8 firmware. Another factor that I did not look into, is the version of the Wemos D1. No clue if this has an influence.

Also the timing between the write and the read request, which is in the code I use is set to 100ms.

There are too many factors … :roll_eyes:

I wonder if there could also be an issue because we use Software Serial.

When I first came upon the -1 issue I knocked up a quick hysteresis algorithm, so any deviation from the last value greater than plus/minus x would cause it to resample, continuing to do so until a valid value was obtained. I still got the -1 coming through so I thought it could be something to with network timing instead, with the database receiving garbage packets and setting them to -1 which would then show on the graph.

For what it’s worth I’ve also seen the PM drop out to zero at random times too so these spikes are not specific to CO2.

@Znook I can also confirm the drops of the PM to zero.

I am pretty confident it is a serial timing issue and as suggested an issue with softserial could be the case. Since about 72 hours I do not see invalid CO2 readings.

The PMS zero spikes increased when I added PM1.0 and PM10 readings. By code change I fixed it by reading the serial data into an array in a single line of code.
My airgradient is reading PM1.0m PM2.5 and PM10 without any issues. at this moment.

I still want to do some more testing with different code.

This are my readings of the last 24 hrs. Nice to recognize when the window was opened and my neighbor started a wood fire in his garden this evening,


As you can see, no invalid CO2 or PMS readings.

1 Like

I have the exact same issue with 1.4.2.

Not only I get spike of 65XXX and -1 but also other spikes at around 17XXX.

Disclaimer: Just build it yesterday. Still need to give it a proper case and placing. So for now, I’m taking the reading with a grain of salt.
Also I bought the kit directly from AirGradient.

@ttielemans I’d be very interested in trying your code that also get the PM 1.0 and PM 10

@Nimrodus

Because I want to pass 5 values (PM1, PM2,5, PM10, temperature and humidity) from my PMS5003 T (which can also measure temperature and humidity), I passs the values by reference.

Code added to Airgradient.cpp

void AirGradient::getPM_Raw(float *pmdata){
  DATA data;
  requestRead();
  if (readUntil(data)) {
    pmdata[0] = data.PM_AE_UG_1_0;
	pmdata[1] = data.PM_AE_UG_2_5;
	pmdata[2] = data.PM_AE_UG_10_0;
	pmdata[3] = data.PM_TMP;
	pmdata[4] = data.PM_HMD;
    return;
  } 
  else {
	pmdata[0] = -99;  // just a value for debugging
	pmdata[1] = -99;
	pmdata[2] = -99;
	pmdata[3] = -99;
	pmdata[4] = -99;
	return;
  }
}

code added to airgradient.h
void getPM_Raw(float *pmdata);

I am using the Jeff Geerling approach so I use the AirGradient-DIY.ino and made the following changes in GenerateMetrics()

String GenerateMetrics() {
...

float statPMS[5];
  if (hasPM) {
    statPMS[0] = -99;
    while (statPMS[0] == -99){
      ag.getPM_Raw(statPMS);
    }
    message += "# HELP Particulate Matter PM1.0 value\n";
    message += "# TYPE pm01 gauge\n";
    message += prefix;
    message += deviceId;
    message += "_pm01";
    message += idString;
    message += String(statPMS[0]);
    message += "\n";

    message += "# HELP Particulate Matter PM2.5 value\n";
    message += "# TYPE pm02 gauge\n";
    message += prefix;
    message += deviceId;
    message += "_pm02";
    message += idString;
    message += String(statPMS[1]);
    message += "\n";
      
    message += "# HELP Particulate Matter PM10 value\n";
    message += "# TYPE pm10 gauge\n";
    message += prefix;
    message += deviceId;
    message += "_pm10";
    message += idString;
    message += String(statPMS[2]);
    message += "\n";
 }