Humidity precision

Hi! I’m wondering if we could get another digit of precision for humidity (presumably via a firmware update). Integer precision is sufficient for display purposes but it’s rather crude when it comes to graphing. Here, you can see the the air conditioner cycling on and off with a device with 0.1% precision, while the 1% precision of the I9-PSL looks like random noise:

It gets worse when you compute absolute humidity or dew point from the relative humidity and temperature. The fine grained temperature changes stack on top of the course grained humidity changes and the whole thing ends up looking kind of janky:

Thanks!

4 Likes

The SHT40 sensor has a noise of 2-4% (https://sensirion.com/products/catalog/SHT40) and as far as I know only provides int.

I believe having a decimal would create a false sense of accuracy.

The SHT40 sensor has a noise of 2-4% (https://sensirion.com/products/catalog/SHT40) and as far as I know only provides int.

I haven’t verified the underlying behavior of the hardware, but the arduino-sht library provides a float, which is initially captured the AirGradient Arduino library but truncated further down the chain.

I believe having a decimal would create a false sense of accuracy.

I think this a good reason not to display it to an end user, but not a good reason to withhold it at an API level. Additional precision still contains useful information even if the accuracy is a bit askew.

I’d also note that the temperature accuracy for the SHT40 is cited as 0.2°C, but the API is providing a granularity of 0.01°C. That’s comparable to what I am requesting.

1 Like

Okay, the datasheet for the SHT40 clarifies that the resolution of the sensor is 0.01% relative humidity and 0.01°C.

1 Like

I’m playing with a SHT40 at the moment. The problem is that, while the resolution is two decimal places to 0.01, the accuracy is only ±1.8%.

From what I can see, it looks like the Arduino library reports to one decimal place, but that’s still of little use if the actual reading can be out by almost 2%.

I’m playing with a SHT40 at the moment. The problem is that, while the resolution is two decimal places to 0.01, the accuracy is only ±1.8%.

Just because the accuracy can be a bit skewed doesn’t mean that precision is useless.

From what I can see, it looks like the Arduino library reports to one decimal place, but that’s still of little use if the actual reading can be out by almost 2%.

The library returns a float. Over the weekend I hacked together a firmware to round it to 0.01% RH (matching the spec in the datasheet) and the results look great to me:

Compare to the image in my original post. Note the noise is very small, well under 1.8%.

Patch, for reference.

I am releasing this under CC BY-SA 4.0 Attribution-ShareAlike 4.0 International License

diff --git a/examples/BASIC/BASIC.ino b/examples/BASIC/BASIC.ino
index 4099b71..1c93f77 100644
--- a/examples/BASIC/BASIC.ino
+++ b/examples/BASIC/BASIC.ino
@@ -554,10 +554,10 @@ static void tempHumUpdate(void) {
     measurements.Humidity = ag.sht.getRelativeHumidity();
 
     Serial.printf("Temperature in C: %0.2f\r\n", measurements.Temperature);
-    Serial.printf("Relative Humidity: %d\r\n", measurements.Humidity);
+    Serial.printf("Relative Humidity: %0.2f\r\n", measurements.Humidity);
     Serial.printf("Temperature compensated in C: %0.2f\r\n",
                   measurements.Temperature);
-    Serial.printf("Relative Humidity compensated: %d\r\n",
+    Serial.printf("Relative Humidity compensated: %0.2f\r\n",
                   measurements.Humidity);
 
     // Update compensation temperature and humidity for SGP41
diff --git a/examples/DiyProIndoorV3_3/DiyProIndoorV3_3.ino b/examples/DiyProIndoorV3_3/DiyProIndoorV3_3.ino
index df059b7..f516d61 100644
--- a/examples/DiyProIndoorV3_3/DiyProIndoorV3_3.ino
+++ b/examples/DiyProIndoorV3_3/DiyProIndoorV3_3.ino
@@ -606,10 +606,10 @@ static void tempHumUpdate(void) {
     measurements.Humidity = ag.sht.getRelativeHumidity();
 
     Serial.printf("Temperature in C: %0.2f\r\n", measurements.Temperature);
-    Serial.printf("Relative Humidity: %d\r\n", measurements.Humidity);
+    Serial.printf("Relative Humidity: %0.2f\r\n", measurements.Humidity);
     Serial.printf("Temperature compensated in C: %0.2f\r\n",
                   measurements.Temperature);
-    Serial.printf("Relative Humidity compensated: %d\r\n",
+    Serial.printf("Relative Humidity compensated: %0.2f\r\n",
                   measurements.Humidity);
 
     // Update compensation temperature and humidity for SGP41
diff --git a/examples/DiyProIndoorV4_2/DiyProIndoorV4_2.ino b/examples/DiyProIndoorV4_2/DiyProIndoorV4_2.ino
index 20be418..cd4ee35 100644
--- a/examples/DiyProIndoorV4_2/DiyProIndoorV4_2.ino
+++ b/examples/DiyProIndoorV4_2/DiyProIndoorV4_2.ino
@@ -649,10 +649,10 @@ static void tempHumUpdate(void) {
     measurements.Humidity = ag.sht.getRelativeHumidity();
 
     Serial.printf("Temperature in C: %0.2f\r\n", measurements.Temperature);
-    Serial.printf("Relative Humidity: %d\r\n", measurements.Humidity);
+    Serial.printf("Relative Humidity: %0.2f\r\n", measurements.Humidity);
     Serial.printf("Temperature compensated in C: %0.2f\r\n",
                   measurements.Temperature);
-    Serial.printf("Relative Humidity compensated: %d\r\n",
+    Serial.printf("Relative Humidity compensated: %0.2f\r\n",
                   measurements.Humidity);
 
     // Update compensation temperature and humidity for SGP41
diff --git a/examples/OneOpenAir/OneOpenAir.ino b/examples/OneOpenAir/OneOpenAir.ino
index 978c345..2ebac97 100644
--- a/examples/OneOpenAir/OneOpenAir.ino
+++ b/examples/OneOpenAir/OneOpenAir.ino
@@ -1048,7 +1048,7 @@ static void updatePm(void) {
       Serial.printf("[1] PM10 ug/m3: %d\r\n", measurements.pm10_1);
       Serial.printf("[1] PM3.0 Count: %d\r\n", measurements.pm03PCount_1);
       Serial.printf("[1] Temperature in C: %0.2f\r\n", measurements.temp_1);
-      Serial.printf("[1] Relative Humidity: %d\r\n", measurements.hum_1);
+      Serial.printf("[1] Relative Humidity: %0.2f\r\n", measurements.hum_1);
       Serial.printf("[1] Temperature compensated in C: %0.2f\r\n",
                     ag->pms5003t_1.temperatureCompensated(measurements.temp_1));
       Serial.printf("[1] Relative Humidity compensated: %f\r\n",
@@ -1078,7 +1078,7 @@ static void updatePm(void) {
       Serial.printf("[2] PM10 ug/m3: %d\r\n", measurements.pm10_2);
       Serial.printf("[2] PM3.0 Count: %d\r\n", measurements.pm03PCount_2);
       Serial.printf("[2] Temperature in C: %0.2f\r\n", measurements.temp_2);
-      Serial.printf("[2] Relative Humidity: %d\r\n", measurements.hum_2);
+      Serial.printf("[2] Relative Humidity: %0.2f\r\n", measurements.hum_2);
       Serial.printf("[2] Temperature compensated in C: %0.2f\r\n",
                     ag->pms5003t_1.temperatureCompensated(measurements.temp_2));
       Serial.printf("[2] Relative Humidity compensated: %d\r\n",
@@ -1217,10 +1217,10 @@ static void tempHumUpdate(void) {
     measurements.Humidity = ag->sht.getRelativeHumidity();
 
     Serial.printf("Temperature in C: %0.2f\r\n", measurements.Temperature);
-    Serial.printf("Relative Humidity: %d\r\n", measurements.Humidity);
+    Serial.printf("Relative Humidity: %0.2f\r\n", measurements.Humidity);
     Serial.printf("Temperature compensated in C: %0.2f\r\n",
                   measurements.Temperature);
-    Serial.printf("Relative Humidity compensated: %d\r\n",
+    Serial.printf("Relative Humidity compensated: %0.2f\r\n",
                   measurements.Humidity);
 
     // Update compensation temperature and humidity for SGP41
diff --git a/src/AgOledDisplay.cpp b/src/AgOledDisplay.cpp
index 8b07ada..94fe843 100644
--- a/src/AgOledDisplay.cpp
+++ b/src/AgOledDisplay.cpp
@@ -38,12 +38,12 @@ void OledDisplay::showTempHum(bool hasStatus) {
 
   /** Show humidty */
   if (utils::isValidHumidity(value.Humidity)) {
-    snprintf(buf, sizeof(buf), "%d%%", value.Humidity);
+    snprintf(buf, sizeof(buf), "%0.0f%%", value.Humidity);
   } else {
     snprintf(buf, sizeof(buf), "-%%");
   }
 
-  if (value.Humidity > 99) {
+  if (strnlen(buf, sizeof(buf)) > 3) {
     DISP()->drawStr(97, 10, buf);
   } else {
     DISP()->drawStr(105, 10, buf);
diff --git a/src/AgValue.cpp b/src/AgValue.cpp
index 015a788..d7252d9 100644
--- a/src/AgValue.cpp
+++ b/src/AgValue.cpp
@@ -43,9 +43,9 @@ String Measurements::toString(bool localServer, AgFirmwareMode fwMode, int rssi,
         }
       }
       if (utils::isValidHumidity(this->Humidity)) {
-        root["rhum"] = this->Humidity;
+        root["rhum"] = ag->round2(this->Humidity);
         if (localServer) {
-          root["rhumCompensated"] = this->Humidity;
+          root["rhumCompensated"] = ag->round2(this->Humidity);
         }
       }
     }
@@ -87,7 +87,7 @@ String Measurements::toString(bool localServer, AgFirmwareMode fwMode, int rssi,
         if (localServer) {
           val = ag->pms5003t_2.humidityCompensated((this->hum_1 + this->hum_2) / 2.0f);
           if (utils::isValidHumidity(val)) {
-            root["rhumCompensated"] = (int)val;
+            root["rhumCompensated"] = ag->round2(val);
           }
         }
       }
@@ -124,12 +124,12 @@ String Measurements::toString(bool localServer, AgFirmwareMode fwMode, int rssi,
           }
         }
         if (utils::isValidHumidity(this->hum_1)) {
-          root["rhum"] = this->hum_1;
+          root["rhum"] = ag->round2(this->hum_1);
 
           if (localServer) {
             val = ag->pms5003t_1.humidityCompensated(this->hum_1);
             if (utils::isValidHumidity(val)) {
-              root["rhumCompensated"] = (int)val;
+              root["rhumCompensated"] = ag->round2(val);
             }
           }
         }
@@ -161,12 +161,12 @@ String Measurements::toString(bool localServer, AgFirmwareMode fwMode, int rssi,
           }
         }
         if(utils::isValidHumidity(this->hum_2)) {
-          root["rhum"] = this->hum_2;
+          root["rhum"] = ag->round2(this->hum_2);
 
           if (localServer) {
             val = ag->pms5003t_2.humidityCompensated(this->hum_2);
             if (utils::isValidHumidity(val)) {
-              root["rhumCompensated"] = (int)val;
+              root["rhumCompensated"] = ag->round2(val);
             }
           }
         }
@@ -199,11 +199,11 @@ String Measurements::toString(bool localServer, AgFirmwareMode fwMode, int rssi,
             }
           }
           if (utils::isValidHumidity(this->hum_1)) {
-            root["rhum"] = this->hum_1;
+            root["rhum"] = ag->round2(this->hum_1);
             if(localServer) {
               val = ag->pms5003t_1.humidityCompensated(this->hum_1);
               if(utils::isValidHumidity(val)) {
-                root["rhumCompensated"] = (int)val;
+                root["rhumCompensated"] = ag->round2(val);
               }
             }
           }
@@ -232,12 +232,12 @@ String Measurements::toString(bool localServer, AgFirmwareMode fwMode, int rssi,
             }
           }
           if (utils::isValidHumidity(this->hum_2)) {
-            root["rhum"] = this->hum_2;
+            root["rhum"] = ag->round2(this->hum_2);
 
             if(localServer) {
               val = ag->pms5003t_1.humidityCompensated(this->hum_2);
               if(utils::isValidHumidity(val)) {
-                root["rhumCompensated"] = (int)val;
+                root["rhumCompensated"] = ag->round2(val);
               }
             }
           }
@@ -269,12 +269,12 @@ String Measurements::toString(bool localServer, AgFirmwareMode fwMode, int rssi,
             }
           }
           if (utils::isValidHumidity(this->hum_1)) {
-            root["channels"]["1"]["rhum"] = this->hum_1;
+            root["channels"]["1"]["rhum"] = ag->round2(this->hum_1);
 
             if (localServer) {
               val = ag->pms5003t_1.humidityCompensated(this->hum_1);
               if (utils::isValidHumidity(val)) {
-                root["channels"]["1"]["rhumCompensated"] = (int)val;
+                root["channels"]["1"]["rhumCompensated"] = ag->round2(val);
               }
             }
           }
@@ -305,12 +305,12 @@ String Measurements::toString(bool localServer, AgFirmwareMode fwMode, int rssi,
             }
           }
           if (utils::isValidHumidity(this->hum_2)) {
-            root["channels"]["2"]["rhum"] = this->hum_2;
+            root["channels"]["2"]["rhum"] = ag->round2(this->hum_2);
 
             if (localServer) {
               val = ag->pms5003t_1.humidityCompensated(this->hum_2);
               if (utils::isValidHumidity(val)) {
-                root["channels"]["2"]["rhumCompensated"] = (int)val;
+                root["channels"]["2"]["rhumCompensated"] = ag->round2(val);
               }
             }
           }
diff --git a/src/AgValue.h b/src/AgValue.h
index 3176482..9dcc8ab 100644
--- a/src/AgValue.h
+++ b/src/AgValue.h
@@ -33,7 +33,7 @@ public:
   ~Measurements() {}
 
   float Temperature;
-  int Humidity;
+  float Humidity;
   int CO2;
   int TVOC;
   int TVOCRaw;
@@ -45,14 +45,14 @@ public:
   int pm10_1;
   int pm03PCount_1;
   float temp_1;
-  int hum_1;
+  float hum_1;
 
   int pm25_2;
   int pm01_2;
   int pm10_2;
   int pm03PCount_2;
   float temp_2;
-  int hum_2;
+  float hum_2;
 
   int pm1Value01;
   int pm1Value25;
2 Likes

Thank you for sharing this. I will discuss with our team.

For what it is worth, the ESPHome firmware is reporting 2 decimal places for Humidity
image

2 Likes

Thank you for the patch. I just got my first AirGradient. While I understand about the absolute accuracy of the sensor, I agree it makes sense to use more of the precision offered by the sensor.