Smart Door Open/Close Sensor Easy

Build a smart door sensor using a reed switch and MQTT

🔗Goal

Build a smart door sensor that detects when a door is opened or closed and sends notifications over MQTT. The system uses a magnetic reed switch mounted on the door frame — when the door is closed, the magnet holds the switch shut. When the door opens, the magnet moves away and the switch opens, triggering the ESP32 to publish a message.

Here is how the system works at a high level:

graph LR
    A[Reed Switch + Magnet] -->|GPIO 14| B[ESP32]
    B -->|WiFi| C[MQTT Broker]
    C --> D[Phone / MQTT Client]
    C --> E[Home Automation]

This is a practical home automation project. Once the ESP32 publishes door events to MQTT, you can use any MQTT-compatible system — MQTT Explorer, Node-RED, Home Assistant, or a custom app — to receive notifications and trigger automations.

🔗How a Reed Switch Works

A reed switch is a small glass tube containing two thin metal contacts (reeds). When a magnet is brought close to the switch, the reeds are pulled together and the circuit closes. When the magnet moves away, the reeds spring apart and the circuit opens.

For a door sensor:

  • Door closed: Magnet is near the reed switch, circuit is closed (reads LOW with INPUT_PULLUP)
  • Door open: Magnet is far from the reed switch, circuit is open (reads HIGH with INPUT_PULLUP)
graph TD
    A[Magnet near switch] -->|Circuit closed| B[GPIO reads LOW]
    B --> C[Door is CLOSED]
    D[Magnet away from switch] -->|Circuit open| E[GPIO reads HIGH]
    E --> F[Door is OPEN]

We use the ESP32's built-in pull-up resistor (INPUT_PULLUP) so no external resistor is needed for the reed switch.

🔗Prerequisites

You will need the following components:

ComponentQtyNotesBuy
ESP32 dev board1AliExpress | Amazon.de .co.uk .com
Magnetic reed switch1Normally open (NO) type, often sold with a magnetAliExpress | Amazon.de .co.uk .com
Breadboard1For prototypingAliExpress | Amazon.de .co.uk .com
Jumper wires~4AliExpress | Amazon.de .co.uk .com

Links marked Amazon/AliExpress are affiliate links. We may earn a small commission at no extra cost to you.

You will also need the following Arduino libraries:

LibraryAuthorPurpose
PubSubClientNick O'LearyMQTT client for Arduino

No external pull-up resistor is needed. We use the ESP32's internal pull-up via INPUT_PULLUP.

🔗Tutorial

🔗Step 1: Wiring

The reed switch has two wires (no polarity — either wire can go to either pin).

ComponentPinESP32 Pin
Reed switchWire 1GPIO 14
Reed switchWire 2GND

That is it — just two connections. With INPUT_PULLUP enabled on GPIO 14, the pin is held HIGH internally. When the reed switch closes (magnet nearby), it connects GPIO 14 to GND, pulling it LOW.

Pin labels and GPIO numbers vary between ESP32 boards. Always check your board's pinout diagram and datasheet.

🔗Step 2: Mount the sensor (or simulate it)

For testing on a breadboard, simply hold the magnet near the reed switch to simulate a closed door, and move it away to simulate an open door.

For permanent installation:

  • Mount the reed switch on the door frame (the stationary part)
  • Mount the magnet on the door itself (the moving part)
  • When the door is closed, the magnet should be within $10$-$15\,\text{mm}$ of the reed switch

🔗Step 3: Upload the code

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

// ----- Configuration -----
const char* WIFI_SSID      = "YOUR_WIFI_SSID";
const char* WIFI_PASSWORD   = "YOUR_WIFI_PASSWORD";
const char* MQTT_BROKER     = "test.mosquitto.org";
const int   MQTT_PORT       = 1883;
const char* MQTT_CLIENT_ID  = "esp32_door_sensor";
const char* MQTT_TOPIC      = "home/door/status";

#define REED_PIN 14

// ----- State -----
int lastDoorState   = -1;  // -1 means "unknown" on startup
unsigned long lastDebounceTime = 0;
const unsigned long DEBOUNCE_DELAY = 50;  // 50ms debounce
int lastRawReading  = -1;

// ----- Objects -----
WiFiClient wifiClient;
PubSubClient mqtt(wifiClient);

// ----- WiFi connection -----
void connectWiFi() {
    if (WiFi.status() == WL_CONNECTED) return;

    Serial.print("Connecting to WiFi");
    WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
    int attempts = 0;
    while (WiFi.status() != WL_CONNECTED && attempts < 40) {
        delay(500);
        Serial.print(".");
        attempts++;
    }
    if (WiFi.status() == WL_CONNECTED) {
        Serial.println();
        Serial.print("Connected! IP: ");
        Serial.println(WiFi.localIP());
    } else {
        Serial.println();
        Serial.println("WiFi connection failed. Will retry...");
    }
}

// ----- MQTT connection -----
void connectMQTT() {
    if (mqtt.connected()) return;

    Serial.print("Connecting to MQTT broker...");
    while (!mqtt.connected()) {
        if (mqtt.connect(MQTT_CLIENT_ID)) {
            Serial.println(" connected!");
        } else {
            Serial.print(" failed (rc=");
            Serial.print(mqtt.state());
            Serial.println("). Retrying in 5 seconds...");
            delay(5000);
        }
    }
}

// ----- Publish door state -----
void publishDoorState(const char* state) {
    // Build JSON payload with state and uptime
    unsigned long uptimeSeconds = millis() / 1000;
    String payload = "{";
    payload += "\"status\":\"" + String(state) + "\",";
    payload += "\"uptime_s\":" + String(uptimeSeconds);
    payload += "}";

    if (mqtt.publish(MQTT_TOPIC, payload.c_str(), true)) {  // retained message
        Serial.print("Published to ");
        Serial.print(MQTT_TOPIC);
        Serial.print(": ");
        Serial.println(payload);
    } else {
        Serial.println("ERROR: MQTT publish failed.");
    }
}

void setup() {
    Serial.begin(115200);

    // Reed switch: one wire to GPIO, other to GND
    // INPUT_PULLUP means pin reads HIGH when switch is open (door open)
    // and LOW when switch is closed (door closed, magnet nearby)
    pinMode(REED_PIN, INPUT_PULLUP);

    connectWiFi();
    mqtt.setServer(MQTT_BROKER, MQTT_PORT);
    connectMQTT();

    // Publish initial state
    int initialState = digitalRead(REED_PIN);
    lastDoorState = initialState;
    if (initialState == LOW) {
        publishDoorState("CLOSED");
        Serial.println("Initial state: CLOSED");
    } else {
        publishDoorState("OPEN");
        Serial.println("Initial state: OPEN");
    }

    Serial.println("Smart Door Sensor ready.");
}

void loop() {
    // Reconnect if needed
    connectWiFi();
    if (!mqtt.connected()) {
        connectMQTT();
    }
    mqtt.loop();

    // ----- Debounced reed switch reading -----
    int currentReading = digitalRead(REED_PIN);

    // If the raw reading changed, reset the debounce timer
    if (currentReading != lastRawReading) {
        lastDebounceTime = millis();
        lastRawReading = currentReading;
    }

    // If the reading has been stable for longer than the debounce delay,
    // accept it as the actual state
    if ((millis() - lastDebounceTime) > DEBOUNCE_DELAY) {
        if (currentReading != lastDoorState) {
            lastDoorState = currentReading;

            if (currentReading == LOW) {
                Serial.println("Door CLOSED");
                publishDoorState("CLOSED");
            } else {
                Serial.println("Door OPEN");
                publishDoorState("OPEN");
            }
        }
    }

    delay(10);  // Small delay to reduce CPU usage
}

🔗Step 4: Test the sensor

  1. Upload the sketch and open the Serial Monitor at 115200 baud
  2. Hold the magnet near the reed switch — the Serial Monitor should show "Door CLOSED"
  3. Move the magnet away — it should show "Door OPEN"
  4. Each state change is published to MQTT

To verify MQTT messages from your computer:

mosquitto_sub -h test.mosquitto.org -t "home/door/status"

You should see JSON messages like:

{"status":"OPEN","uptime_s":45}
{"status":"CLOSED","uptime_s":52}

The message is published with the retained flag, so new subscribers immediately receive the last known state even if no state change has occurred recently.

🔗Step 5: Understanding debounce

Mechanical switches "bounce" — when the contacts open or close, they can rapidly toggle several times before settling. Without debouncing, a single door open might register as multiple events.

The code debounces by requiring the reading to remain stable for $50\,\text{ms}$ before accepting it as a valid state change. This timing diagram shows why:

sequenceDiagram
    participant Door
    participant Reed Switch
    participant ESP32
    participant MQTT

    Door->>Reed Switch: Door opens (magnet moves away)
    Reed Switch->>ESP32: Signal bounces HIGH/LOW/HIGH
    Note over ESP32: Wait 50ms for stable reading
    ESP32->>ESP32: Reading stable at HIGH
    ESP32->>MQTT: Publish "OPEN"

🔗Next Steps: Deep Sleep for Battery Operation

If you want to run this sensor on a battery, the ESP32's deep sleep mode can dramatically reduce power consumption. Instead of constantly polling the reed switch, the ESP32 sleeps and wakes up only when the pin state changes.

Deep sleep reduces current draw from approximately $40\,\text{mA}$ (active WiFi) to about $10\,\mu\text{A}$. This can extend battery life from days to months.

The trade-off: after waking from deep sleep, the ESP32 must reconnect to WiFi and MQTT, which takes 2-5 seconds. For a door sensor, this delay is usually acceptable.

Here is the concept — replace the loop() function with a wake-on-pin approach:

// At the end of setup(), after publishing the initial state:
// Configure GPIO 14 to wake from deep sleep on state change
esp_sleep_enable_ext0_wakeup(GPIO_NUM_14, lastDoorState == LOW ? 1 : 0);
// 1 = wake when pin goes HIGH (door opens)
// 0 = wake when pin goes LOW (door closes)

Serial.println("Entering deep sleep. Will wake on door state change.");
Serial.flush();
esp_deep_sleep_start();

With this approach, the ESP32 wakes up, reads the door state, connects to WiFi, publishes the MQTT message, and goes back to sleep. The loop() function never runs because setup ends with deep sleep.

🔗Common Issues and Solutions

ProblemCauseFix
Door state does not changeMagnet too far from reed switchBring the magnet within $10$-$15\,\text{mm}$ of the switch. Reed switches have a limited activation distance
Multiple MQTT messages on one open/closeSwitch bounceThe code includes debouncing ($50\,\text{ms}$). If you still see duplicates, increase DEBOUNCE_DELAY to 100
State is inverted (shows OPEN when closed)Reed switch type is normally closed (NC) instead of normally open (NO)Swap the logic: LOW means OPEN and HIGH means CLOSED, or use an NO reed switch
WiFi reconnection is slowNormal behavior after deep sleep or WiFi dropoutWiFi reconnection takes 2-5 seconds. The code retries automatically
MQTT publishes failBroker unreachable or WiFi not connectedCheck that WiFi is connected before publishing. The code handles reconnection in loop()
Sensor works on breadboard but not on doorWires too long or poor connectionUse shielded wire for runs longer than $1\,\text{m}$. Solder connections instead of using breadboard jumpers
Battery drains in one dayESP32 is not entering deep sleepImplement the deep sleep approach from the "Next Steps" section. Active WiFi draws about $40\,\text{mA}$