HVAC monitoring with IoT sensors gives you a continuous view of how air handlers, fan coils and rooftop units actually behave, not just whether they are switched on. This use case shows which measurements catch the most common faults, how to publish them over MQTT, and how to turn them into alerts and runtime reports with a cloud broker.
What Goes Wrong in HVAC Equipment
Most HVAC problems do not announce themselves with an alarm. They show up as slow drifts: a filter loads up and airflow drops, a valve sticks and coil performance falls, a compressor short-cycles because of a bad thermostat location, or a unit runs all weekend because a schedule was overridden and never reset. Occupants notice only once comfort suffers, and by then energy has been wasted for weeks.
A building management system can detect some of this, but many sites have no BMS at all, or have one that only a contractor can access. Retrofitting a few IoT sensors and publishing to MQTT fills the gap quickly and works the same across a whole portfolio, whether the unit is a rooftop package unit at a retail store or an air handler in an office tower.
The Signals That Matter
You do not need dozens of points per unit. The measurements below cover most practical fault detection:
| Signal | Sensor | What it reveals |
|---|---|---|
| Supply air temperature | Duct probe (DS18B20, PT1000 with transmitter) | Whether the unit is actually heating or cooling |
| Return air temperature | Duct probe | Load on the unit, with supply gives delta-T |
| Filter differential pressure | Low-range differential pressure sensor, 0 to 500 Pa | Filter loading, missing filter, belt failure |
| Fan or compressor current | Split-core current transformer | Runtime, short cycling, mechanical load changes |
| Outdoor air temperature | Shielded probe or weather feed | Context for heating and cooling demand |
| Zone temperature | Wall sensor | Whether the space is comfortable |
The most useful derived value is delta-T, the difference between return and supply air. When a cooling unit runs with a very small delta-T, either the coil is not getting chilled water or refrigerant, the airflow is far too high, or a sensor is in the wrong place. A filter pressure that climbs steadily over weeks is normal; one that suddenly drops toward zero means the filter has collapsed or is missing.
Architecture
There are two common ways to get these signals onto MQTT:
- Existing controllers. If the units already have BACnet controllers, a gateway reads the points and publishes them, as shown in our BACnet to MQTT tutorial.
- Retrofit sensor nodes. An ESP32 or a small industrial IoT module per unit reads the probes directly and publishes over Wi-Fi or Ethernet. The ESP32 MQTT tutorial covers the connection basics.
In both cases the node publishes outbound to a broker, and every consumer subscribes: the portal topic explorer for commissioning, an analytics script for derived values, and a dashboard for facility staff. Topic structure follows the building hierarchy:
acme/ist01/tower-a/roof/ahu-2/telemetry
acme/ist01/tower-a/roof/ahu-2/derived
acme/ist01/tower-a/roof/ahu-2/status
A single telemetry message per interval keeps traffic small and makes time alignment easy:
{
"ts": "2026-10-16T10:30:00Z",
"supply_c": 13.6,
"return_c": 23.1,
"outdoor_c": 27.4,
"filter_dp_pa": 182,
"fan_a": 6.8,
"comp_a": 14.2,
"mode": "cooling"
}
Computing Delta-T and Runtime from MQTT
Derived values are best computed in one place rather than in each sensor node. The script below subscribes to every unit's telemetry, calculates delta-T, accumulates fan and compressor runtime, counts compressor starts per hour to detect short cycling, and publishes the results back to MQTT. Try it on the free public MQTT broker with the demo prefix, then switch to private credentials.
import json
import time
import uuid
from collections import defaultdict, deque
import paho.mqtt.client as mqtt
BROKER, PORT = "broker.connectmq.com", 1883
PREFIX = "connectmq/demo/hvac-q81z"
FAN_ON_A = 1.0 # current above this means the fan runs
COMP_ON_A = 3.0 # current above this means the compressor runs
MAX_STARTS_PER_HOUR = 6
state = defaultdict(lambda: {"last_ts": None, "fan_s": 0.0, "comp_s": 0.0,
"comp_on": False, "starts": deque()})
def on_connect(client, userdata, flags, reason_code, properties):
print("connected:", reason_code)
client.subscribe(f"{PREFIX}/+/telemetry", qos=0)
def on_message(client, userdata, msg):
unit = msg.topic.split("/")[-2]
try:
t = json.loads(msg.payload)
except json.JSONDecodeError:
return
s = state[unit]
now = time.time()
if s["last_ts"] is not None:
dt = min(now - s["last_ts"], 120) # cap gaps so outages do not inflate runtime
if t.get("fan_a", 0) > FAN_ON_A:
s["fan_s"] += dt
if s["comp_on"]:
s["comp_s"] += dt
s["last_ts"] = now
comp_on = t.get("comp_a", 0) > COMP_ON_A
if comp_on and not s["comp_on"]:
s["starts"].append(now)
s["comp_on"] = comp_on
while s["starts"] and now - s["starts"][0] > 3600:
s["starts"].popleft()
delta_t = None
if "return_c" in t and "supply_c" in t:
delta_t = round(t["return_c"] - t["supply_c"], 2)
derived = {
"ts": t.get("ts"),
"delta_t_c": delta_t,
"fan_runtime_h": round(s["fan_s"] / 3600, 2),
"comp_runtime_h": round(s["comp_s"] / 3600, 2),
"comp_starts_last_hour": len(s["starts"]),
"short_cycling": len(s["starts"]) > MAX_STARTS_PER_HOUR,
}
client.publish(f"{PREFIX}/{unit}/derived", json.dumps(derived), qos=1, retain=True)
client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2, client_id=f"hvac-analytics-{uuid.uuid4().hex[:6]}")
client.on_connect = on_connect
client.on_message = on_message
client.connect(BROKER, PORT, keepalive=60)
client.loop_forever()
Simulate a unit from the command line to see it work:
mosquitto_pub -h broker.connectmq.com -p 1883 -i hvac-sim-q81z \
-t "connectmq/demo/hvac-q81z/ahu-2/telemetry" \
-m '{"ts":"2026-10-16T10:30:00Z","supply_c":13.6,"return_c":23.1,"filter_dp_pa":182,"fan_a":6.8,"comp_a":14.2}'
The runtime counters here live in memory, so they reset when the script restarts. For billing-grade runtime, write the derived values to a time-series database from the same subscriber, or let a tool like Telegraf subscribe to the topics and store them.
Threshold Alerts for HVAC Faults
ConnectMQ lets you define threshold alert rules on incoming values in the portal, and every triggered alert is kept in the alerts log. Practical starting rules for a cooling air handler:
| Rule | Condition | Probable cause |
|---|---|---|
| Low delta-T while cooling | delta_t_c below 4 for 30 minutes |
No chilled water, stuck valve, excessive airflow |
| Filter loaded | filter_dp_pa above the filter's final pressure drop |
Filter replacement due |
| Filter missing or belt broken | filter_dp_pa below 20 while the fan runs |
Collapsed filter, slipped belt |
| Supply air too warm | supply_c above 18 in cooling mode |
Compressor fault, refrigerant loss |
| Short cycling | comp_starts_last_hour above 6 |
Oversized unit, thermostat placement, control fault |
| Off-hours operation | fan current above threshold outside schedule | Overridden schedule |
Tune thresholds per unit after a week of observation. Equipment, climate and filter class vary too much for universal numbers, and the manufacturer data sheet gives the right filter pressure limits.
Storing History and Reporting
Live values catch faults; history proves savings and guides maintenance. Because every consumer is just another MQTT client, adding storage does not touch the sensor nodes. Common choices are a Telegraf instance with its MQTT consumer input writing to InfluxDB, a Node-RED flow inserting into PostgreSQL, or a small Python subscriber writing to whatever database your organization already runs. Store raw telemetry at the native interval for a few months and keep hourly aggregates for years.
Useful monthly reports for facility teams include runtime hours per unit compared with scheduled hours, the number of hours each unit spent with a low delta-T, the date each filter crossed its replacement pressure, and units ranked by compressor starts. These numbers turn vague complaints such as "the third floor is always warm" into specific work orders.
Rollout Checklist
- Choose three to five representative units, not the whole portfolio.
- Mount supply probes far enough downstream of the coil for mixed air, and return probes before the mixing box.
- Fit current transformers on a single conductor, not the whole cable.
- Commission each sensor by watching live values in the topic explorer while the unit cycles.
- Run the analytics subscriber and observe for a week before enabling alerts.
- Write a short runbook for each alert so technicians know what to check.
- Expand to the rest of the units once false alarms are under control.
Scaling Up
A single retrofit node per unit and one analytics service fit easily into the free Serverless plan for a pilot. As you add buildings, connection count grows linearly with units, and the paid Serverless tier supports up to 1,000 connections with shared subscriptions, so you can run several analytics workers that split the load. Because the broker is a cloud MQTT broker you do not install anything in each building except the sensor nodes.
Air quality and occupancy add valuable context to HVAC data: see indoor air quality monitoring and office occupancy sensors. The broader picture is covered in building automation system MQTT and the IoT automation guide.
Start Monitoring Your HVAC with ConnectMQ
Create a free Serverless broker at app.connectmq.com, no credit card required. Connect a sensor node, watch the values in the live topic explorer, then add threshold alert rules for delta-T and filter pressure. ConnectMQ runs the broker so your team can focus on the equipment.
Frequently Asked Questions
What is the single most useful HVAC measurement?
Supply and return air temperature together, because delta-T shows whether the unit is actually transferring heat. Add filter differential pressure next.
Do I need a BMS for HVAC monitoring with IoT sensors?
No. Retrofit sensor nodes can publish directly to MQTT. If a BMS exists, a gateway can reuse its points instead of installing new sensors.
How often should HVAC telemetry be sent?
Every 30 to 60 seconds is enough for temperatures and pressures. Current measurements for runtime and short cycling benefit from 10 to 15 second intervals.
Can alerts be sent from the broker itself?
ConnectMQ evaluates threshold alert rules and records them in the alerts log. For SMS, email or ticketing, run a subscriber such as Node-RED that reacts to the relevant topics.



