>>> Join Us in the Fight Against Air Pollution

AirGradient Forum

PM2.5 value often -1

In Grafana I see the pm2.5 value often being -1.

01_before_change

Currently in use:

ESP8266 Arduino library (Core)						v3.1.2
Libraries:
AirGradient Air Quality Sensor						v2.4.3
ESP8266 and ESP32 OLED driver for SSD1306 displays	v4.4.0
Sensirion Core 										v0.6.0
Sensirion Gas Index Algorithm						v3.2.2
Sensirion I2C SGP41									v0.1.0
U8g2												v2.32.15
WiFiManager											v2.0.15-rc.1

On both the OLED display and in Grafana I often see the value -1 for pm2.5 measurements.
I’m using Themi changes which intergrade a webserver for Prometheus
In the Grafana graphs it showed the -1 value about once or twice every hour before I made any changes.
After trying to adapt the config to extend lifespan of PMS5003 I get the -1 value far more often (e.g. about 1/5th of the time).

02_after_change
03_after_change_zoom

Changes:
(If I did something incorrect please let me know. I’m not familiar with C++ at all.)

// const int pm25Interval = 5000;
const int pm25Interval = 30000;
const int pm25SleepIntvl = 150000;
unsigned long previousPm25 = 0;
unsigned long previousSleepPm25 = 0;
int pm25 = 0;

void sleepPm25()
{
    if (currentMillis - previousSleepPm25 >= pm25SleepIntvl) {
      previousSleepPm25 += (pm25SleepIntvl + pm25Interval);
      ag.wakeUp();
    }
}

void updatePm25()
{
    if (currentMillis - previousPm25 >= pm25SleepIntvl + pm25Interval) {
      previousPm25 += (pm25SleepIntvl + pm25Interval);
      pm25 = ag.getPM2_Raw();
      Serial.println(String(pm25));
      ag.sleep();
    }
}

I have not found a meaning for the -1 value. I assume it indicates a kind of error state?
Is this a known behavior? Or might this be an issue with the PMS5003 module itself?

I have another device with a PMS5003 module, so I temporarily swapped the two modules.
I still see the same behavior, so likely not hardware related.

Here a graph of ~2 hours before and after the swap.
Swap PMS5003 modules 2023-05-28

Just noticed that the fan constantly going ‘on’ and ‘off’ (e.g. 2-3 seconds ‘on’, 2-3 seconds ‘off’ continuously).
This starts after a few minutes from power-on e.g. likely after a PM2.5 read cycle.

Edit:
Reverted the extended lifespan changes for the PMS5003, and now the fan is behaving normal again.

A time-interval issue caused the ‘on’/‘off’ behaviour. Which looks ok now.

Still see the -1 values, but not so often anymore.
(top graph is from another PM2.5 monitor, bottom the PM2.5 graph of the AirGradient)

The applied changes from Themi, and a few from me (PMS5003 extended lifespan).

--- DIY_PRO_V3_7.ino_v2.4.3.txt 2023-04-29 01:50:56.000000000 +0200
+++ DIY_PRO_V3_7.ino_v2.4.3_2023-06-01_v1.5.txt     2023-06-01 12:44:11.000000000 +0200
@@ -27,6 +27,8 @@
 
 */
 
+// Added by Themi
+#include <ESP8266WebServer.h>
 
 #include <AirGradient.h>
 #include <WiFiManager.h>
@@ -51,7 +53,7 @@
 // time in seconds needed for NOx conditioning
 uint16_t conditioning_s = 10;
 
-// for peristent saving and loading
+// for persistent saving and loading
 int addr = 4;
 byte value;
 
@@ -64,6 +66,14 @@
 
 // CONFIGURATION START
 
+// Added by Themi
+// Set to true if you'd like to report to an airgradient endpoint
+boolean doReport = true;
+// Http port number
+const int port = 8080;
+// Prometheus device id
+const String deviceId = "AirGradient1";
+
 //set to the endpoint you would like to use
 String APIROOT = "http://hw.airgradient.com/";
 
@@ -76,11 +86,13 @@
 // Display Position
 boolean displayTop = true;
 
-// set to true if you want to connect to wifi. You have 60 seconds to connect. Then it will go into an offline mode.
+// set to true if you want to connect to wifi. You have 90 seconds to connect. Then it will go into an offline mode.
 boolean connectWIFI=true;
 
 // CONFIGURATION END
 
+// Added by Themi
+ESP8266WebServer server(port);
 
 unsigned long currentMillis = 0;
 
@@ -99,9 +111,13 @@
 unsigned long previousCo2 = 0;
 int Co2 = 0;
 
-const int pm25Interval = 5000;
+//const int pm25Interval = 5000;
+const int pm25Interval = 30000;
+const int pm25SleepIntvl = 150000;
 unsigned long previousPm25 = 0;
+//unsigned long previousSleepPm25 = 0;
 int pm25 = 0;
+boolean pm25sleep = false;
 
 const int tempHumInterval = 2500;
 unsigned long previousTempHum = 0;
@@ -148,6 +164,59 @@
   ag.CO2_Init();
   ag.PMS_Init();
   ag.TMP_RH_Init(0x44);
+
+  // Added by Themi
+  server.on("/metrics", HandleMetrics);
+  server.on("/metricsjson", HandleJsonMetrics);
+  server.on("/", HandleIndexHTML);
+  server.onNotFound(HandleNotFound);
+  server.begin();
+}
+
+// Added by Themi
+void HandleMetrics() {
+  String message = "";
+  message += GetPrometheusString("pm02 Particulate Matter PM2.5 value", "pm02 gauge", "pm02",  "µg/m³", String(pm25) );
+  message += GetPrometheusString("rco2 CO2 value, in ppm", "rco2 gauge", "rco2", "ppm", String(Co2) );
+  message += GetPrometheusString("Temperature, in degrees Celcius", "temp gauge", "temp", "°C", String(temp) );
+  message += GetPrometheusString("rhum Relative humidity, in percent", "rhum gauge", "rhum", "%", String(hum) );
+  message += GetPrometheusString("TVOC index value", "tvoc gauge", "tvoc", "index", String(TVOC) );
+  message += GetPrometheusString("NOX index value", "nox gauge", "nox", "index", String(NOX) );
+  server.send(200, "text/plain", message);
+}
+
+void HandleJsonMetrics() {
+  String message = "{\"pm02\":"+ String(pm25) + ",\"rco2\":" + String(Co2) + ",\"temp\":" + String(temp) + ",\"rhum\":" + String(hum) + ",\"tvoc\":" + String(TVOC) + ",\"nox\":" + String(NOX) + "}";
+  server.send(200, "application/json", message);
+}
+
+void HandleIndexHTML() {
+  String message = "<!doctype html>";
+  message += "<title>CO2-Monitor</title>";
+  message += "<section class=\"content\">";
+  message += "  <h1>Welcome to CO2-Monitor</h1>";
+  message += "  <p>I'm a C++ script to received sensor data from the CO2-Monitor and make it available via HTTP</p>";
+  message += "  <p>For prometheus metrics, see <a href=\"/metrics\">/metrics</a></p>";
+  message += "  <p>For the raw JSON data try <a href=\"/metricsjson\">/metricsjson</a></p>";
+  server.send(200, "text/html", message);
+}
+
+// Added by Themi
+String GetPrometheusString(String proHelp, String proType, String proMetric, String proUnit, String proStat) {
+  String idString = "{id=\"" + String(deviceId) + "\",name=\"" + proType + "\",unit=\"" + proUnit + "\"}";
+  String proOut = "";
+  proOut += "# HELP " + proHelp + "\n";
+  proOut += "# TYPE " + proType + "\n";
+  proOut += proMetric;
+  proOut += idString;
+  proOut += proStat;
+  proOut += "\n";
+  return proOut;
+}
+
+// Added by Themi
+void HandleNotFound() {
+  server.send(404, "text/plain", "Not found");  
 }
 
 void loop() {
@@ -155,10 +224,16 @@
   updateTVOC();
   updateOLED();
   updateCo2();
+  //
+  sleepPm25();
   updatePm25();
   updateTempHum();
+  // Added by Themi
+  if (doReport) {
   sendToServer();
 }
+  server.handleClient();
+}
 
 void inConf(){
   setConfig();
@@ -307,12 +382,26 @@
     }
 }
 
+void sleepPm25()
+{
+    if (currentMillis - previousPm25 >= pm25SleepIntvl && pm25sleep) {
+// This will not work because it is true also 
+//      previousSleepPm25 = currentMillis;
+      ag.wakeUp();
+         pm25sleep = false;
+    }
+}
+
 void updatePm25()
 {
-    if (currentMillis - previousPm25 >= pm25Interval) {
-      previousPm25 += pm25Interval;
+    if (currentMillis - previousPm25 >= pm25SleepIntvl + pm25Interval) {
+      previousPm25 = currentMillis;
+//    if (currentMillis - previousPm25 >= pm25Interval) {
+//      previousPm25 = currentMillis;
       pm25 = ag.getPM2_Raw();
       Serial.println(String(pm25));
+      ag.sleep();
+         pm25sleep = true;
     }
 }
 
@@ -403,6 +492,17 @@
    updateOLED2("90s to connect", "to Wifi Hotspot", HOTSPOT);
    wifiManager.setTimeout(90);
 
+
+
+
+
+
+
+
+
+
+
+
    if (!wifiManager.autoConnect((const char * ) HOTSPOT.c_str())) {
      updateOLED2("booting into", "offline mode", "");
      Serial.println("failed to connect and hit timeout");