IoT Automation

Office Occupancy Sensor Networks with MQTT: PIR, mmWave and CO2

How to choose between PIR, mmWave and CO2 sensing, design MQTT topics for rooms and desks, and turn occupancy data into utilization reports and HVAC savings.

Meeting room ceiling with a small office occupancy sensor sending presence data to a cloud dashboard
Run this on your own MQTT brokerGet a private, managed MQTT broker on the Free plan and start from ready-made code samples.
Try it free Code samples No card required

An office occupancy sensor network tells you which rooms and desks are actually used, and when. In this use case you will compare PIR, mmWave and CO2-based sensing, publish occupancy over MQTT with a clean topic design, and use the data for space utilization reports and demand-based HVAC control on a ConnectMQ broker.

Why Measure Occupancy

Hybrid work has made office use unpredictable. Meeting rooms booked in the calendar sit empty, while small focus rooms are always full. Floors are heated and cooled to full schedule even on days when a fraction of staff come in. Occupancy data answers practical questions: which rooms are oversized, whether a floor can be closed on Fridays, and whether ventilation can be reduced in empty zones.

Booking systems and badge data give part of the picture, but they record intent and entry, not actual presence in a room. Sensors fill that gap, and MQTT is a natural way to collect their data across many buildings.

Choosing the Right Sensing Technology

Each technology has a characteristic failure mode, and the best deployments combine two.

Technology How it works Strengths Weaknesses Best for
PIR Detects moving heat sources Cheap, low power, mature Misses people sitting still, so rooms look empty during focused work Corridors, toilets, lighting control
mmWave radar Detects tiny movements including breathing Detects stationary people, works through thin covers Can see through walls and pick up neighbors if not tuned; needs mains or large battery Meeting rooms, focus rooms, desks
CO2 Rising CO2 concentration indicates people breathing Gives a rough count and air quality at the same time Slow (minutes), depends on ventilation rate Ventilation control, large rooms
Desk sensors PIR or thermal under the desk Desk-level utilization Many devices to maintain Hot-desking analytics
Thermal array / people counters Low-resolution heat image or doorway counting Counts people without images Higher cost, careful placement Large open spaces, entrances

A common and robust combination for meeting rooms is mmWave for presence plus CO2 for confirmation and ventilation. PIR alone tends to report a room as empty during long video calls because people barely move.

Privacy first

Occupancy data is about people. Use sensors that do not capture images, publish presence or counts rather than identities, and never put names or badge IDs in topics. Aggregate desk data before sharing it widely, and check local regulations and works council agreements before deployment.

MQTT Topic and Payload Design

Model topics on the physical hierarchy so wildcards answer real questions:

acme/ist01/hq/f03/meeting-301/occupancy        presence state (retained)
acme/ist01/hq/f03/meeting-301/co2              CO2 ppm
acme/ist01/hq/f03/desk-3-14/occupancy          desk presence (retained)

A presence message should carry the state, the source and when it last changed:

{
  "ts": "2026-10-16T09:41:07Z",
  "occupied": true,
  "source": "mmwave",
  "since": "2026-10-16T09:12:30Z",
  "count_estimate": null
}

Publish occupancy as retained so a dashboard that connects mid-day shows current state immediately. Publish on change plus a heartbeat every few minutes, which keeps traffic tiny and still lets you detect a dead sensor. With the folder-style hierarchy, acme/ist01/hq/f03/+/occupancy returns every room and desk on floor three, and the ConnectMQ devices view can group the same topics by site and area.

A Simple mmWave Sensor Node

Popular low-cost mmWave modules such as the LD2410 family expose a digital output pin that goes high when presence is detected. An ESP32 can debounce that pin and publish changes. The sketch uses PubSubClient and the public broker for testing.

#include <WiFi.h>
#include <PubSubClient.h>

const char* WIFI_SSID = "YOUR_WIFI";
const char* WIFI_PASS = "YOUR_WIFI_PASSWORD";
const char* BROKER = "broker.connectmq.com";
const int   PORT = 1883;
const char* TOPIC = "connectmq/demo/occ-5t2w/f03/meeting-301/occupancy";
const int   PRESENCE_PIN = 27;           // LD2410 OUT pin
const unsigned long HOLD_MS = 60000;     // stay "occupied" 60 s after last detection
const unsigned long HEARTBEAT_MS = 300000;

WiFiClient net;
PubSubClient mqtt(net);
bool occupied = false;
unsigned long lastSeen = 0, lastPublish = 0;

void publishState() {
  char msg[96];
  snprintf(msg, sizeof(msg), "{\"occupied\":%s,\"source\":\"mmwave\",\"uptime_s\":%lu}",
           occupied ? "true" : "false", millis() / 1000);
  mqtt.publish(TOPIC, msg, true);        // retained
  lastPublish = millis();
}

void connectMqtt() {
  while (!mqtt.connected()) {
    String id = "occ-" + String((uint32_t)ESP.getEfuseMac(), HEX);
    if (mqtt.connect(id.c_str())) {
      publishState();
    } else {
      delay(2000);
    }
  }
}

void setup() {
  pinMode(PRESENCE_PIN, INPUT);
  WiFi.begin(WIFI_SSID, WIFI_PASS);
  while (WiFi.status() != WL_CONNECTED) delay(250);
  mqtt.setServer(BROKER, PORT);
}

void loop() {
  if (!mqtt.connected()) connectMqtt();
  mqtt.loop();

  if (digitalRead(PRESENCE_PIN) == HIGH) lastSeen = millis();
  bool now = (millis() - lastSeen) < HOLD_MS && lastSeen != 0;

  if (now != occupied) {
    occupied = now;
    publishState();
  } else if (millis() - lastPublish > HEARTBEAT_MS) {
    publishState();
  }
  delay(100);
}

The hold time is the most important tuning parameter. Too short and the room flickers between occupied and empty; too long and the room looks busy after people leave. Sixty seconds to a few minutes works well for meeting rooms. Tune the radar's detection range with the vendor tool so it does not see into the corridor.

From Raw Presence to Utilization

Raw on/off messages become useful once aggregated. A small subscriber can compute, per room and per day, the share of working hours that the room was occupied. A minimal Python version:

import json
import time
import uuid
from collections import defaultdict

import paho.mqtt.client as mqtt

PREFIX = "connectmq/demo/occ-5t2w"
occupied_since = {}
occupied_seconds = defaultdict(float)


def on_connect(client, userdata, flags, reason_code, properties):
    client.subscribe(f"{PREFIX}/+/+/occupancy", qos=1)


def on_message(client, userdata, msg):
    room = "/".join(msg.topic.split("/")[-3:-1])
    data = json.loads(msg.payload)
    now = time.time()
    if data["occupied"] and room not in occupied_since:
        occupied_since[room] = now
    elif not data["occupied"] and room in occupied_since:
        occupied_seconds[room] += now - occupied_since.pop(room)
    hours = occupied_seconds[room] / 3600
    client.publish(f"{PREFIX}/{room}/utilization",
                   json.dumps({"occupied_hours_today": round(hours, 2)}), retain=True)


client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2, client_id=f"occ-agg-{uuid.uuid4().hex[:6]}")
client.on_connect = on_connect
client.on_message = on_message
client.connect("broker.connectmq.com", 1883, 60)
client.loop_forever()

In production, store the transitions in a database so you can report weekly peaks and compare rooms by size.

Using Occupancy for HVAC Control

Occupancy data pays back fastest when it drives ventilation and temperature setbacks:

  1. When a zone has been empty for a set period, a gateway can widen its temperature deadband or reduce airflow.
  2. When CO2 rises above your target, increase ventilation regardless of presence state.
  3. Before scheduled occupancy, pre-condition the space as usual; occupancy only trims, never replaces, the schedule.
  4. Always keep local control safe: the HVAC controller should fall back to its schedule if occupancy data stops arriving.

The write-back path to the BMS is the same one described in BACnet to MQTT, and the HVAC side of the story is covered in HVAC monitoring with IoT sensors. CO2 measurement doubles as an air quality signal; see our indoor air quality monitor build.

Validating Sensor Accuracy

Before anyone builds a report on occupancy data, check that the sensors tell the truth. A simple validation week works well. Pick a handful of rooms and ask a colleague to note actual use at fixed times, or compare sensor data with calendar bookings and look for disagreements. Typical findings are mmWave sensors that report a room occupied because they see movement in the corridor, PIR sensors that report empty rooms during long calls, and CO2 sensors that lag by ten minutes or more after people arrive.

Fix each finding at the source: reduce the radar's detection range, move a PIR so it faces the seating area rather than the door, or use CO2 only as a confirmation signal. Record the final settings per room type so new installations start from a known-good configuration. Validation also builds trust with the people whose rooms are being measured, which matters as much as technical accuracy.

Deployment Checklist

  1. Survey rooms and decide technology per room type.
  2. Mount mmWave sensors facing into the room, away from doors and windows.
  3. Give each sensor a unique client ID and credentials on a private broker.
  4. Commission each room by walking in and out while watching the topic explorer.
  5. Run for two weeks before drawing utilization conclusions.

Testing on the public MQTT test broker is fine with a demo prefix, but real occupancy data reveals when people are in the building, so it belongs on a private broker over TLS. A managed MQTT broker from ConnectMQ gives every sensor its own credentials, and a few dozen sensors fit comfortably into a pilot. For more building projects, see the IoT automation guide.

Run Your Occupancy Sensors on ConnectMQ

Sign up at app.connectmq.com and create a free Serverless broker, no credit card required. Connect your first sensors, watch presence change live in the topic explorer, and add threshold alert rules for CO2 in busy rooms. ConnectMQ handles the broker so you can focus on the space.

Frequently Asked Questions

Which office occupancy sensor is most accurate for meeting rooms?

mmWave radar is usually the best single choice because it detects people sitting still. Combining it with CO2 adds confirmation and supports ventilation control.

Do occupancy sensors capture personal data?

PIR, mmWave and CO2 sensors do not capture images or identities, but occupancy patterns are still sensitive. Publish only presence or counts and keep the data on a private broker.

How long should the hold time be?

Start with 60 seconds for small rooms and up to a few minutes for large ones, then adjust after watching real data for a week.

Can occupancy data reduce HVAC energy use?

Yes, by setting back temperature and ventilation in empty zones. Keep the controller's schedule as a fallback so comfort never depends on the sensor network.

Related articles

Zigbee2MQTT Setup Guide: From Coordinator to Dashboard

Install Zigbee2MQTT with Docker, configure the coordinator and MQTT connection, pair and rename devices, control them over MQTT and view their states on a dashboard, testing first on a free public broker.

7 min read

All articles →

Your private MQTT broker is a few clicks away

Create an account, get your broker credentials and connect your first device — no servers to install, patch or scale.