This is article 4 of 8 in a series about robust DS18B20 / 1-Wire system design.
Please see the introductory topic:
Detect degrading sensors in long-term operation
Occasional read errors are normal when working with DS18B20 sensors, especially in electrically noisy environments or long cable installations.
Typical error values include:
- −127 °C → communication error
- +85 °C → sensor startup value
The usual approach is to sanitize such readings and keep the system running safely.
Many systems even simply ignore occasional invalid readings or retry the measurement.
However, for systems that operate months or years, another question becomes relevant:
Is this sensor still healthy, or is it slowly degrading?
The approach described here tries to answer exactly that question by continuously evaluating the reliability of each sensor during normal operation.
When is Sensor Health Monitoring Useful?
Not every DS18B20 setup requires sensor health monitoring.
If your sensor is connected with a short cable in a quiet electrical environment and you rarely see invalid readings, the basic temperature reading is usually sufficient.
However, this approach becomes very useful in systems where sensors run unattended for long periods of time, for example:
- heating or solar thermal systems
- aquariums and environmental monitoring
- industrial or agricultural installations
- remote or hard-to-access locations
- long cable runs or electrically noisy environments
In these situations occasional read errors are normal and usually not a problem by themselves.
What becomes important is detecting whether a sensor is gradually becoming less reliable over time.
The health monitoring presented here focuses exactly on that:
tracking long-term reliability trends while the system continues to operate normally.
To my knowledge this is the first lightweight health monitoring approach specifically designed for DS18B20 long-term deployments.
Details
A single error means nothing. But patterns of errors over time can reveal a sensor that is beginning to fail.
This module continuously evaluates such patterns and produces a simple result:
Sensor Health = 0 … 100 %
| Health | Interpretation |
|---|---|
| 90–100 % | excellent |
| 75–90 % | normal operation |
| 60–75 % | monitor |
| 40–60 % | consider replacement |
| <40 % | replacement recommended |
The monitoring works with minimal integration effort.
Quick Integration
Typical integration requires only two additional lines.
tempC = sensors.getTempC... // as usual
float sanitized = sanitizeTemp(tempC); // normalize errors
feedSafeTempModel(sanitized); // optional runtime safety
feedSensorHealthMonitoring(sanitized); // health monitoring
Whenever needed:
currentSensorHealth = getSensorHealth();
API Overview
initSensorHealthMonitoring();
feedSensorHealthMonitoring(sanitizedValue, sensorID); // sensorID optional
feedSensorHealthMonitoringExtraPenalty(penalty); // affects all sensors
feedSensorHealthMonitoringExtraPenalty(penalty, sensorID);
getSensorHealth(sensorID);
Sanitizing Sensor Values
The monitoring module expects NaN for invalid readings.
float sanitizeTemp(float t)
{
// sensor error code (exactly -127.0)
if (t == -127.0) return NAN;
// sensor start value (exactly 85.0, almost never a real temperature)
if (t == 85.0) return NAN;
// physically no valid values
// DS18B20 nominally: -55 to +125 °C (use at least this as default!)
// depending on your application choose your limits for a plausibility check
if (t < 10.0 || t > 90.0) return NAN; // as an example!
// just a valid value
return t;
}
Configuration Parameters
Users may adjust the sensitivity via a few parameters.
#define ERROR_ALPHA 0.98
#define HEALTH_CLUSTER_THRESHOLD 3
#define HEALTH_CLUSTER_PENALTY 3
#define HEALTH_MAX_AUTO_PENALTY 1
ERROR_ALPHA
Controls how quickly old errors are forgotten.
| Value | Behavior |
|---|---|
| 0.90 | reacts very quickly |
| 0.95 | moderate |
| 0.98 | stable (recommended) |
| 0.995 | very tolerant |
HEALTH_CLUSTER_THRESHOLD
Number of consecutive errors required to trigger a cluster penalty.
#define HEALTH_CLUSTER_THRESHOLD 3 // default
| Value | Sensitivity |
|---|---|
| 2 | Aggressive - small number of errors triggers penalty quickly |
| 3 | Balanced - default, moderate sensitivity |
| 5 | Mild - only repeated errors trigger penalty, more tolerant |
HEALTH_CLUSTER_PENALTY
Penalty applied when such a cluster occurs.
#define HEALTH_CLUSTER_PENALTY 3 // default
| Value | Effect |
|---|---|
| 2 | Mild - small deduction per cluster |
| 3 | Balanced - default, moderate deduction |
| 5 | Aggressive - strong deduction per cluster |
| 10 | Extreme - very strong penalty for each cluster |
HEALTH_MAX_AUTO_PENALTY
Limits automatic penalty per cycle.
#define HEALTH_MAX_AUTO_PENALTY 1
This prevents the health value from collapsing too quickly.
Manual Sensor Priority
Some sensors may be more critical than others.
Example:
| Sensor | Importance |
|---|---|
| Solar collector | low |
| Hot water | medium |
| Safety shutdown | very high |
If a critical sensor misbehaves, the user may apply additional penalties.
feedSensorHealthMonitoringExtraPenalty(2, 3);
Meaning:
Sensor #3 loses 2 additional health points
Example:
if (crcError)
{
feedSensorHealthMonitoringExtraPenalty(3, 3);
}
Penalty Recommendation Table
| Event | Recommended Penalty |
|---|---|
| Bus reset detected | 3–5 |
| temporary sensor missing | 5–8 |
| CRC error | 2–4 |
| manual sensor priority | user defined |
| major communication fault | 5–10 |
Example:
feedSensorHealthMonitoringExtraPenalty(5);
Persistent Storage in EEPROM
Sensor degradation is a long-term process.
Therefore the health values should survive MCU resets and power loss.
This module periodically stores the health values in EEPROM.
However, EEPROM lifetime must be considered.
Typical Arduino EEPROM endurance:
100,000 write cycles per cell
To ensure extremely long lifetime, writes are performed only every few hours.
EEPROM Configuration
#define HEALTH_EEPROM_INTERVAL 21600000UL // 6 hours
#define HEALTH_EEPROM_START_ADDR 0
#define HEALTH_EEPROM_MAGIC_ADDR 20
#define HEALTH_EEPROM_MAGIC_VALUE 0xA5
HEALTH_EEPROM_INTERVAL
Interval between EEPROM updates.
21600000 ms = 6 hours
Worst-case write count over 20 years:
4 writes per day
≈ 1460 writes per year
≈ 29200 writes in 20 years
This is far below the 100k EEPROM limit, providing a very large safety margin.
Users therefore do not need to worry about EEPROM wear.
Magic Pattern
When the system starts, the EEPROM content must be validated.
A magic pattern is used to detect whether valid health data is present.
0xA5
If the magic value is missing, the system assumes:
EEPROM contains no valid health data
and initializes the health values to 100%.
Integration Steps for Existing Sketches
The monitoring module was designed so that integration into existing projects requires only a few small steps.
1. Add the Module Code
Copy the Sensor Health Monitoring section from this article into your sketch.
2. Initialize the Monitoring System
In setup() add:
initSensorHealthMonitoring();
Example:
void setup()
{
Serial.begin(9600);
sensors.begin();
initSensorHealthMonitoring();
}
3. Feed the Monitoring System
After reading the sensor value:
float tempC = sensors.getTempCByIndex(0);
float sanitized = sanitizeTemp(tempC);
feedSensorHealthMonitoring(sanitized);
For multiple sensors:
feedSensorHealthMonitoring(sanitized_3, 3);
If the Safe Temperature Model is already integrated
If your project already uses the Safe Temperature Model from the previous article, the integration is even simpler.
In that case the sanitized value already exists and is used for the model, so the health monitoring can be fed immediately afterwards.
Example:
float tempC = sensors.getTempCByIndex(0);
float sanitized = sanitizeTemp(tempC);
feedSafeTempModel(sanitized); // existing safety model
feedSensorHealthMonitoring(sanitized); // health monitoring
4. Optional Manual Penalties
Whenever the system detects additional faults:
feedSensorHealthMonitoringExtraPenalty(3, sensorID);
Example:
if(busResetDetected)
{
feedSensorHealthMonitoringExtraPenalty(5);
}
5. Read the Health Value
Whenever needed:
uint8_t health = getSensorHealth(0);
Complete Example Sketch (Ready to Compile)
#include <OneWire.h>
#include <DallasTemperature.h>
#include <EEPROM.h>
#define ONE_WIRE_BUS 2
OneWire oneWire(ONE_WIRE_BUS);
DallasTemperature sensors(&oneWire);
/* -------------------------------------------------------
Configuration
------------------------------------------------------- */
#define MAX_SENSORS 4
#define ERROR_ALPHA 0.98
#define HEALTH_CLUSTER_THRESHOLD 3
#define HEALTH_CLUSTER_PENALTY 3
#define HEALTH_MAX_AUTO_PENALTY 1
#define HEALTH_EEPROM_INTERVAL 21600000UL
#define HEALTH_EEPROM_START_ADDR 0
#define HEALTH_EEPROM_MAGIC_ADDR 20
#define HEALTH_EEPROM_MAGIC_VALUE 0xA5
/* -------------------------------------------------------
Global Variables
------------------------------------------------------- */
uint8_t sensorHealth[MAX_SENSORS];
uint8_t consecutiveErrors[MAX_SENSORS];
float errorRate[MAX_SENSORS];
unsigned long lastEEPROMWrite = 0;
/* -------------------------------------------------------
sanitizeTemp()
------------------------------------------------------- */
float sanitizeTemp(float t)
{
// sensor error code (exactly -127.0)
if (t == -127.0) return NAN;
// sensor start value (exactly 85.0, almost never a real temperature)
if (t == 85.0) return NAN;
// physically no valid values
// DS18B20 nominally: -55 to +125 °C (use at least this as default!)
// depending on your application choose your limits for a plausibility check
if (t < 10.0 || t > 90.0) return NAN; // as an example!
// just a valid value
return t;
}
/* -------------------------------------------------------
EEPROM Handling
------------------------------------------------------- */
void loadHealthFromEEPROM()
{
if(EEPROM.read(HEALTH_EEPROM_MAGIC_ADDR) != HEALTH_EEPROM_MAGIC_VALUE)
{
for(int i=0;i<MAX_SENSORS;i++)
sensorHealth[i] = 100;
return;
}
for(int i=0;i<MAX_SENSORS;i++)
{
uint8_t v = EEPROM.read(HEALTH_EEPROM_START_ADDR + i);
if(v <= 100)
sensorHealth[i] = v;
else
sensorHealth[i] = 100;
}
}
void saveHealthToEEPROM()
{
for(int i=0;i<MAX_SENSORS;i++)
EEPROM.update(HEALTH_EEPROM_START_ADDR + i, sensorHealth[i]);
EEPROM.update(HEALTH_EEPROM_MAGIC_ADDR, HEALTH_EEPROM_MAGIC_VALUE);
}
/* -------------------------------------------------------
Initialization
------------------------------------------------------- */
void initSensorHealthMonitoring()
{
for(int i=0;i<MAX_SENSORS;i++)
{
sensorHealth[i] = 100;
consecutiveErrors[i] = 0;
errorRate[i] = 0;
}
loadHealthFromEEPROM();
}
/* -------------------------------------------------------
Monitoring Input
------------------------------------------------------- */
void feedSensorHealthMonitoring(float value, uint8_t sensorID = 0)
{
bool error = isnan(value);
float newError = error ? 1.0 : 0.0;
errorRate[sensorID] =
errorRate[sensorID] * ERROR_ALPHA +
newError * (1.0 - ERROR_ALPHA);
if(error)
{
consecutiveErrors[sensorID]++;
if(consecutiveErrors[sensorID] >= HEALTH_CLUSTER_THRESHOLD)
{
if(sensorHealth[sensorID] > HEALTH_CLUSTER_PENALTY)
sensorHealth[sensorID] -= HEALTH_CLUSTER_PENALTY;
}
}
else
{
consecutiveErrors[sensorID] = 0;
}
uint8_t penalty = (uint8_t)(errorRate[sensorID] * 20);
if(penalty > HEALTH_MAX_AUTO_PENALTY)
penalty = HEALTH_MAX_AUTO_PENALTY;
if(sensorHealth[sensorID] > penalty)
sensorHealth[sensorID] -= penalty;
}
/* -------------------------------------------------------
Manual Penalties
------------------------------------------------------- */
void feedSensorHealthMonitoringExtraPenalty(uint8_t penalty)
{
for(int i=0;i<MAX_SENSORS;i++)
if(sensorHealth[i] > penalty)
sensorHealth[i] -= penalty;
}
void feedSensorHealthMonitoringExtraPenalty(uint8_t penalty, uint8_t sensorID)
{
if(sensorHealth[sensorID] > penalty)
sensorHealth[sensorID] -= penalty;
}
/* -------------------------------------------------------
Query Health
------------------------------------------------------- */
uint8_t getSensorHealth(uint8_t sensorID = 0)
{
return sensorHealth[sensorID];
}
/* -------------------------------------------------------
Setup
------------------------------------------------------- */
void setup()
{
Serial.begin(115000);
sensors.begin();
initSensorHealthMonitoring();
}
/* -------------------------------------------------------
Loop
------------------------------------------------------- */
void loop()
{
sensors.requestTemperatures();
float tempC = sensors.getTempCByIndex(0);
float sanitized = sanitizeTemp(tempC);
feedSensorHealthMonitoring(sanitized);
if(millis() - lastEEPROMWrite > HEALTH_EEPROM_INTERVAL)
{
saveHealthToEEPROM();
lastEEPROMWrite = millis();
}
uint8_t health = getSensorHealth();
Serial.print("Temp: ");
Serial.print(tempC);
Serial.print(" Health: ");
Serial.print(health);
Serial.println("%");
delay(2000);
}
Result
Combined with the introduced Safe Temperature Model, this approach provides:
sanitized sensor readings
fail-safe temperature modeling
long-term sensor health monitoring
persistent degradation tracking
This significantly improves reliability for long-running installations and noisy sensor networks. +++ Use at you own risk! +++
Feedback and Long-Term Testing
This monitoring system is designed to evaluate sensor behavior over very long periods of time.
Because of that, changes in the reported Sensor Health value are intentionally slow. In normal operation it may take many hours, days, or even weeks before any noticeable degradation appears.
Please keep this in mind when testing the system. Short experiments usually will not show meaningful health changes, which is by design — the algorithm is meant to detect long-term reliability trends, not temporary glitches.
If you try this module in your own project, your feedback would be extremely valuable. In particular, it would help the community if you could share:
- your application scenario (number of sensors, cable lengths, environment, etc.)
- how often you typically see invalid readings
- whether you are using extra penalties and under which conditions
- how you tuned the health parameters
- any interesting long-term behavior you observe
Since many installations run continuously for months or years, real-world feedback from different setups is very helpful for understanding how the algorithm behaves under diverse conditions.
Think of this as a community field test. Even small observations can help improve recommendations for parameter settings and usage patterns.
Thank you to everyone who takes the time to test this and report back!