Your IoT Dashboard Says the Device Is Online. But Is It Actually Alive?
Introduction
Imagine monitoring 500 IoT devices through a single dashboard.
Everything looks healthy. Most devices have a green ONLINE indicator, and there are no visible connection errors.
But an operator reports that one of the devices has not updated its sensor readings for the last 30 minutes.
You investigate and discover something unexpected: the MQTT broker still considers the device connected.
The dashboard says ONLINE, but the device is no longer delivering useful data.
This is a common design problem in IoT monitoring systems. Many applications treat device connectivity and device health as the same thing.
However, they represent different conditions.
A device can be connected to an MQTT broker but have an unresponsive application. It can publish telemetry regularly while reporting a hardware fault. It can also disconnect unexpectedly while the dashboard continues displaying its previous status.
A connected device is not necessarily a healthy device.
In this article, we will explore how to design a more reliable device-health monitoring mechanism using MQTT, Python, FastAPI, and a state-based monitoring approach.
1. Why MQTT Connection Status Is Not Device Health
MQTT is widely used in IoT systems because it provides lightweight publish-subscribe communication between devices and backend services.
A typical architecture looks like this:
IoT Device
|
| MQTT Connection
|
v
MQTT Broker
|
| Telemetry / Heartbeat
|
v
Backend Application
|
v
Monitoring Dashboard
A common implementation uses MQTT connection events to determine whether a device is online.
For example:
- When a device connects, mark it
ONLINE. - When it disconnects, mark it
OFFLINE.
This is useful, but it does not provide a complete picture of device health.
Consider three situations.
Scenario A: Connected but not publishing
A device successfully connects to the broker. However, its firmware encounters an internal problem and stops publishing telemetry.
The broker may still maintain the connection.
The backend continues showing the device as online even though its application is no longer functioning correctly.
Scenario B: Publishing but reporting a fault
A temperature-monitoring device publishes telemetry every minute, but its sensor reports an invalid reading.
From a connectivity perspective, the device is working.
From an operational perspective, it may be unhealthy.
Scenario C: Stale dashboard state
A device loses connectivity unexpectedly. The backend does not process its disconnect event immediately, or the event is missed during a service restart.
The dashboard continues displaying the last known state.
These scenarios demonstrate why a single Boolean field is insufficient for production monitoring.
Instead of asking only whether a device is connected, we should ask:
- Has the backend received recent telemetry?
- Is the device responding within its expected interval?
- Is the application reporting internal faults?
- Has the device recovered after an interruption?
These are separate signals and should be modeled separately.
2. Designing a Better Device Health State Model
A practical approach is to introduce a device-health state machine.
For example:
| State | Meaning |
|---|
| UNKNOWN | No reliable health information is available |
| ONLINE | Recent communication has been received |
| DEGRADED | Communication is delayed or inconsistent |
| OFFLINE | Communication has exceeded the allowed timeout |
These states provide more information than a simple online/offline flag.
Understanding each state
UNKNOWN
A device has been registered but has not yet sent its first heartbeat or telemetry message.
This is different from offline because the backend has not established a reliable baseline.
ONLINE
The device has communicated within its expected reporting interval.
DEGRADED
The device has missed its expected reporting window but has not exceeded the configured offline threshold.
This state helps identify intermittent connectivity without immediately generating an offline alert.
OFFLINE
The device has not communicated within the allowed timeout period.
The backend should consider it unavailable for operations that require a live connection.
A simplified transition model looks like this:
First heartbeat
UNKNOWN -------------> ONLINE
|
|
Reporting delayed
|
v
DEGRADED
|
|
Timeout exceeded
|
v
OFFLINE
|
|
New heartbeat
|
v
ONLINE
A production implementation may need additional transitions, such as MAINTENANCE, DISABLED, or ERROR.
The important point is to define what each state means instead of allowing different backend services to interpret device status independently.
3. Implementing Heartbeat-Based Health Monitoring in Python
Let's implement a simple health evaluator.
Assume devices are expected to send a heartbeat every 60 seconds.
We can introduce a grace period to account for network delays.
For example:
- Up to 60 seconds:
ONLINE - Between 60 and 90 seconds:
DEGRADED - Beyond 90 seconds:
OFFLINE
The thresholds should be configurable rather than hardcoded for every device.
Step 1: Define device states
from enum import Enum
class DeviceState(str, Enum):
ONLINE = "ONLINE"
DEGRADED = "DEGRADED"
OFFLINE = "OFFLINE"
UNKNOWN = "UNKNOWN"
Step 2: Build the health evaluator
from datetime import datetime, timezone
def evaluate_device_health(
last_seen: datetime | None,
heartbeat_interval: int = 60,
grace_multiplier: float = 1.5
) -> DeviceState:
if last_seen is None:
return DeviceState.UNKNOWN
if last_seen.tzinfo is None:
raise ValueError(
"last_seen must be timezone-aware"
)
now = datetime.now(timezone.utc)
elapsed = (
now - last_seen.astimezone(timezone.utc)
).total_seconds()
if elapsed < 0:
return DeviceState.UNKNOWN
degraded_after = heartbeat_interval
offline_after = (
heartbeat_interval * grace_multiplier
)
if elapsed <= degraded_after:
return DeviceState.ONLINE
if elapsed <= offline_after:
return DeviceState.DEGRADED
return DeviceState.OFFLINE
This function evaluates device health using the last time the backend received a message.
For a 60-second heartbeat interval, the result is:
| Time since last message | Health state |
|---|
| 30 seconds | ONLINE |
| 60 seconds | ONLINE |
| 75 seconds | DEGRADED |
| 90 seconds | DEGRADED |
| 120 seconds | OFFLINE |
This is a simplified implementation, but it demonstrates the core idea.
Step 3: Record server-side reception time
One important detail is where the timestamp comes from.
Do not blindly trust the device's local clock.
An IoT device may have an incorrect system clock, an unstable RTC, or no reliable time synchronization.
Instead, record when the backend receives the message.
For example:
from datetime import datetime, timezone
async def process_telemetry(
device_id: str,
payload: dict
):
received_at = datetime.now(timezone.utc)
await telemetry_repository.save(
device_id=device_id,
payload=payload,
received_at=received_at
)
await device_repository.update_last_seen(
device_id=device_id,
last_seen=received_at
)
The backend's received_at timestamp provides a consistent basis for freshness calculations.
If the device also sends its own timestamp, retain it separately when useful for diagnosing device clock problems.
It is important not to confuse device event time with server reception time. They answer different questions.
4. Building a Scalable Device Health Monitoring Service
Calculating device health is relatively simple.
The bigger challenge appears when monitoring hundreds or thousands of devices.
Imagine a platform with 5,000 devices, each sending a heartbeat every minute.
The backend must:
- Receive incoming messages.
- Update device freshness information.
- Determine whether a device has become degraded or offline.
- Publish status changes to the dashboard.
- Generate alerts when required.
- Recover the device state when communication resumes.
A useful architecture separates telemetry ingestion from health evaluation.
IoT Devices
|
| MQTT
v
MQTT Broker
|
v
Telemetry Ingestion
|
+---------+----------+
| |
v v
Telemetry Store Last Seen Store
|
v
Health Evaluator
|
+-------------+-------------+
| |
v v
Health Database Event Publisher
|
+---------+---------+
| |
v v
Dashboard Alerts
Telemetry ingestion
The ingestion service receives device messages and records the server-side reception timestamp.
It should avoid performing expensive operations inside the MQTT message callback.
For larger systems, telemetry processing can be moved into a queue so that ingestion and downstream processing can scale independently.
Last-seen tracking
Maintain a current last-seen timestamp for each device.
A relational database can store this information, while Redis can be useful for frequently accessed, short-lived freshness data.
For example, a simplified Redis record might look like:
device:1001:last_seen = 2026-11-14T05:30:00Z
device:1002:last_seen = 2026-11-14T05:30:12Z
device:1003:last_seen = 2026-11-14T05:29:45Z
The exact storage choice depends on the workload, durability requirements, and query patterns.
Redis should not automatically become the only source of truth for important device history. If the system requires durable auditing or historical reporting, persist the relevant events in a database.
Health evaluation
There are two common approaches.
Approach A: Scheduled evaluation
A background process periodically checks devices whose last-seen timestamps are approaching their timeout thresholds.
This is relatively straightforward and works well for many systems.
However, repeatedly scanning every device can become expensive.
Use indexed timestamps, bounded batches, or a due-time scheduling strategy rather than performing unrestricted full-table scans.
Approach B: Scheduled expiry
Another approach is to schedule an expiry event when a heartbeat arrives.
If another heartbeat arrives, the scheduled evaluation can be refreshed or invalidated using a version number or another reliable mechanism.
This can reduce unnecessary checks, but it introduces additional complexity around duplicate messages, stale scheduled events, and recovery.
For most applications, I would start with scheduled evaluation and optimize only after measuring the actual workload.
5. Preventing False Offline Alerts
One of the biggest problems in IoT monitoring is false alarms.
A device may miss a heartbeat because of:
- Temporary network congestion.
- Weak cellular connectivity.
- Broker interruptions.
- Power fluctuations.
- Network handovers.
- Firmware scheduling delays.
If the backend immediately marks a device offline after missing one expected heartbeat, operators may receive too many unnecessary alerts.
This creates alert fatigue.
Use a grace period
Instead of treating every missed heartbeat as an offline event, introduce a configurable timeout.
For example:
heartbeat_interval = 60
degraded_after = 60
offline_after = 120
The backend first marks the device degraded and only later marks it offline.
Use hysteresis
Another useful technique is hysteresis.
Consider a device that repeatedly sends messages around the timeout boundary.
Without transition rules, its state might change frequently:
ONLINE
|
DEGRADED
|
ONLINE
|
DEGRADED
|
ONLINE
This makes dashboards difficult to interpret and can generate unnecessary notifications.
A more stable approach may require multiple successful heartbeats before recovering from DEGRADED or OFFLINE.
For example:
- One missed heartbeat moves the device to
DEGRADED. - A longer timeout moves it to
OFFLINE. - Two consecutive successful heartbeats restore it to
ONLINE.
The exact thresholds depend on device behavior and the cost of false positives versus delayed detection.
Handle MQTT Last Will and Testament
MQTT provides another useful mechanism: Last Will and Testament (LWT).
When a client connects, it can register a message that the broker publishes if the connection is lost unexpectedly.
For example:
Topic:
devices/device-1001/status
Payload:
{
"status": "OFFLINE"
}
This allows the backend to receive a broker-generated notification when an unexpected disconnection occurs.
However, LWT should complement heartbeat-based monitoring, not replace it.
A device may remain connected while its application stops functioning.
LWT detects unexpected connection loss; heartbeat monitoring helps detect missing application-level communication.
Also, the backend should handle retained status messages, reconnects, and duplicate or delayed status events carefully. A delayed offline notification should not overwrite newer evidence that the device has recovered.
6. Separating Connectivity, Freshness, and Operational Health
At this point, we have a better health model, but there is another important design decision.
Should every device condition be stored in a single status column?
For simple systems, that may be sufficient.
For larger IoT platforms, separating the dimensions is more useful.
Consider this example:
{
"device_id": "device-1001",
"connectivity": "CONNECTED",
"freshness": "STALE",
"operational_health": "WARNING",
"last_seen": "2026-11-14T05:30:00Z",
"last_heartbeat": "2026-11-14T05:30:00Z"
}
These fields communicate different things:
Connectivity
Indicates the communication connection state, based on broker or transport observations.
Freshness
Indicates whether recent telemetry or heartbeat messages have been received within the expected interval.
Operational health
Indicates whether the device reports errors, sensor faults, low battery, or other operational problems.
This separation allows more meaningful monitoring.
For example:
| Connectivity | Freshness | Operational health | Interpretation |
|---|
| Connected | Fresh | Healthy | Device is communicating normally |
| Connected | Stale | Unknown | Connected, but expected data is missing |
| Disconnected | Stale | Unknown | Device is unavailable |
| Connected | Fresh | Warning | Device communicates but reports a fault |
A dashboard can then display a more useful health summary without hiding the underlying signals.
This also helps backend services make better decisions.
For example, a device can be connected but operationally unhealthy. The system may still allow diagnostic commands while preventing normal production operations.
Conclusion
A green ONLINE indicator does not necessarily mean an IoT device is healthy.
It may only mean that a communication connection was established at some point.
Reliable fleet monitoring requires a more complete understanding of device behavior.
A practical implementation should:
- Separate broker connectivity from application-level health.
- Track server-side message reception timestamps.
- Use configurable heartbeat intervals and grace periods.
- Introduce explicit device-health states.
- Avoid false alerts through hysteresis and recovery rules.
- Combine MQTT Last Will and Testament with application-level heartbeat monitoring.
- Scale health evaluation using indexed timestamps and background processing.
The most important design principle is simple:
Do not confuse a successful connection with a healthy system.
An IoT monitoring platform should tell operators not only whether a device is connected, but also whether it is communicating consistently and functioning as expected.
That distinction becomes increasingly important as device fleets grow and monitoring moves from development environments into production infrastructure.
Arun PottiPosted Nov 14, 2026, 8:39 PM
Great Article.