Energy Monitoring System Calibration: Linear Regression Approach for Sensor Accuracy
How an ESP32 energy monitoring system calibrates DHT11 temperature and humidity readings with linear regression, and how the fitted model runs in production firmware.
An energy monitoring system is only as trustworthy as the data it collects. When the readings feed HVAC control, small sensor errors compound into real misallocation of energy. This note describes how the Eco Office monitor, an ESP32-based system, calibrates its DHT11 temperature and humidity sensors against a reference instrument using ordinary least squares regression, and how the fitted model is deployed directly in the firmware.
The DHT11 is inexpensive and easy to interface, but its accuracy is limited. Compared against a calibrated HTC-1 reference, the raw readings showed a mean absolute error of 3.84 degrees Celsius in temperature and 14.18% in relative humidity. Those offsets are too large for a system that uses environmental data to regulate cooling and heating.
Why Calibration Was Needed
Before calibration, the raw DHT11 readings diverged from the reference across the whole measurement range:
- Temperature: mean absolute error around 3.84 degrees Celsius, with maximum errors approaching 5 degrees
- Humidity: mean absolute error around 14.18%, with large spikes at the extremes
- A consistent negative bias in temperature and a positive bias in humidity
Errors of this size would distort any downstream calculation, from thermal comfort classification to energy consumption analysis. The readings had to be corrected before they could be used.
Measurement Setup and Data Collection
The DHT11 and the HTC-1 were placed side by side in a naturally ventilated office in Indonesia and logged concurrently for about nine hours. Logging spanned warm and cool parts of the day so that the model would see realistic variation.
In total, 19 paired readings were collected. Temperature ranged from the mid-20s to the upper-20s degrees Celsius and relative humidity from around 46% to the low 70s. That spread is sufficient to fit a first-order model and to check how the correction behaves across the operating range.
Calibration Model
For each variable, a first-order model maps a raw reading onto a corrected value :
where is the slope and is the intercept, fitted separately for temperature and humidity. The coefficients fitted from the paired data and later embedded in the firmware are:
The quality of the correction is reported as the mean absolute error (MAE) against the reference:
Model Deployment in Firmware
The firmware stores the four fitted coefficients as constants and applies them on every DHT11 read, so each measurement is corrected before it is used or published:
const float TEMP_SLOPE = 0.923;
const float TEMP_INTERCEPT = -1.618;
const float HUM_SLOPE = 0.926;
const float HUM_INTERCEPT = 18.052;
float calibrateTemperature(float rawTemp) { return (TEMP_SLOPE * rawTemp) + TEMP_INTERCEPT; }
float calibrateHumidity(float rawHum) { return (HUM_SLOPE * rawHum) + HUM_INTERCEPT; }
A multiply-add per reading is negligible on the ESP32, which is one of the reasons a linear model was chosen over a heavier approach.
The firmware also keeps a ring buffer of the last 10 per-sample errors and reports a running mean absolute error. That value is converted into an accuracy figure over the measurement range:
This gives the dashboard a single percentage that summarizes how close the calibrated readings are to the reference.
Results
After applying the fitted models, the error metrics improved substantially:
Temperature
- Mean absolute error dropped from 3.84 degrees Celsius to 0.80 degrees Celsius
- Maximum error dropped from roughly 5 degrees to about 1.4 degrees
- The negative bias disappeared and the residuals scattered evenly around zero
Humidity
- Mean absolute error dropped from 14.18% to 1.15%
- Maximum error shrank to roughly 2%
- Consistency across the range improved noticeably
Overall, temperature accuracy improved by about 80% and humidity accuracy by more than 90%. For a low-cost sensor, that brings the readings within a useful range for energy analysis.
The Analysis Code
The regression was fit with a small Python class. The coefficients below match the values deployed in the firmware:
import numpy as np
from sklearn.linear_model import LinearRegression
from sklearn.metrics import mean_absolute_error, r2_score
class SensorCalibrator:
def __init__(self):
self.temp_model = LinearRegression()
self.humidity_model = LinearRegression()
# Coefficients from the regression analysis
self.temp_coef = 0.923
self.temp_intercept = -1.618
self.humidity_coef = 0.926
self.humidity_intercept = 18.052
def calibrate_temperature(self, raw_temp):
return self.temp_coef * raw_temp + self.temp_intercept
def calibrate_humidity(self, raw_humidity):
return self.humidity_coef * raw_humidity + self.humidity_intercept
def evaluate_calibration(self, raw_readings, reference_readings):
calibrated = [self.calibrate_temperature(t) for t in raw_readings]
mae = mean_absolute_error(reference_readings, calibrated)
r2 = r2_score(reference_readings, calibrated)
return {
'mae': mae,
'r2': r2,
'calibrated_readings': calibrated
}
A quick example:
calibrator = SensorCalibrator()
raw_temp = 25.0
calibrated_temp = calibrator.calibrate_temperature(raw_temp)
print(f"Raw: {raw_temp} C -> Calibrated: {calibrated_temp:.2f} C")
Downstream Use in the System
Calibrated values feed several consumers inside the Eco Office monitor:
- A fuzzy Mamdani engine classifies thermal comfort into COLD, COOL, COMFORTABLE, WARM, and HOT states from temperature and humidity.
- A second fuzzy engine classifies energy consumption into ECONOMICAL, NORMAL, and WASTEFUL states using voltage, active power, power factor, and reactive power from the PZEM-004T.
- MQTT telemetry publishes the corrected readings every 30 seconds to the Selene platform.
- The on-device LCD and the Blynk widgets display the calibrated values and the fuzzy states.
The PZEM-004T measures voltage, current, active power, power factor, frequency, and cumulative energy. One unit note matters here: the library method PZEM004Tv30::energy() returns kilowatt-hours directly, while the underlying register is stored in watt-hours and divided by 1000. The MQTT field, the Blynk widget, and the LCD all use kWh consistently.
Final Thoughts
Linear regression turned out to be a simple and effective way to calibrate low-cost sensors:
- It reduced temperature and humidity errors to a fraction of their original values
- A multiply-add per reading is cheap enough for embedded hardware
- The resulting coefficients are easy to inspect, test, and update in the firmware
- Corrected readings made the downstream fuzzy classification and telemetry substantially more reliable
The next step is to explore non-linear calibration and automatic recalibration, particularly to handle sensor aging and slow changes in environmental conditions.