Siddhant Kumar
Project 055 · Security

Door/Window Breach Alarm.

Magnetic contacts on doors and windows that instantly sound a networked, zoned alarm when one is opened — with supervision so a defeated or dead sensor is never a silent blind spot.

Beginner 8–14 hours 32 min read MagnetAlarmMesh
Jump to source Bill of materials
Door/Window Breach Alarm — reference build illustration MCU VCC · GND · SIG · NC
Difficulty
Beginner
Build time
8–14 hours
Indicative cost
₹2,200 – ₹3,800
Platform
ESP32 DevKit V1 (ESP-WROOM-32)
Category
Security
Last updated
28 July 2026
Contents — 26 sections

Project Overview

Magnetic contacts on doors and windows that instantly sound a networked, zoned alarm when one is opened — with supervision so a defeated or dead sensor is never a silent blind spot.

The most reliable intrusion detection is also the simplest: a magnetic contact that knows whether a door or window is open or shut. A magnet on the moving part and a reed switch on the frame keep a circuit made while the two are together; open the door and the circuit breaks. It is fast, unambiguous, cheap and — done right — very hard to fool, which is why magnetic contacts are the backbone of virtually every real alarm system. This project builds a proper multi-zone version: contacts on every protected opening, an ESP32 that watches them all, and a networked alarm that sounds instantly and tells you which door or window was breached.

The step from a toy to a real alarm is in the details that a bare reed switch skips. Zones: each contact is its own zone so the alarm is located — "kitchen window", not just "alarm" — which matters for response and for finding the breach. Arm/disarm: the system has states (armed-away, armed-home, disarmed) with an entry/exit delay so you can leave and enter through a designated door without tripping it, and it distinguishes perimeter openings from interior movement. And critically, supervision: the wiring is monitored (classically with an end-of-line resistor) so that cutting or shorting a sensor loop — the obvious way to defeat a contact — is detected as a fault and alarmed, rather than silently disabling that zone.

Because it is networked, a breach does more than sound a local siren: it pushes an instant located alert to your phone or a control panel, logs the event, and can trigger other actions (lights, cameras). It supports battery backup so a power cut does not disarm it, and it supervises the sensors themselves so a flat wireless contact or a broken wire raises a maintenance warning before an intruder finds it. It is honest that magnetic contacts detect opening, not glass being smashed or a wall being breached (which is why they are layered with glass-break and motion sensors in a complete system), and that a DIY alarm is not a professionally-monitored installation. But as a fast, located, supervised, networked perimeter alarm, it is exactly the dependable first line that catches the overwhelmingly common intrusion: someone opening a door or window that should be shut.

An ESP32 development board with the ESP-WROOM-32 module and USB connector
Magnetic contacts on doors and windows give fast, hard-to-fool detection of an opening. Photograph sourced from Wikimedia Commons — ESP32 Espressif ESP-WROOM-32 Dev Board.jpg. Reused under the licence stated on that page; please check it before republishing.

What this project does

  • Detects a door/window opening instantly with magnetic contacts
  • Treats each contact as a located zone (which opening was breached)
  • Supports armed-away/armed-home/disarmed with entry/exit delays
  • Supervises the sensor loops so a cut/shorted wire is a fault, not a silent gap
  • Sounds a local siren and pushes an instant located alert / logs it
  • Runs on mains with battery backup so a power cut does not disarm it
  • Distinguishes perimeter openings from interior zones

Real-World Applications

SettingHow it is used
Home intrusion alarmContacts on all external doors and windows, zoned and armable, with phone alerts and a siren.
Small shop / officePerimeter protection with located alarms and an audit of open/close events.
Storeroom / cabinet monitoringAlerting whenever a specific door or cabinet is opened, armed or always.
Vacation / second-home watchRemote monitoring of a property's openings with battery backup and instant alerts.

Deployment contexts where a build of this kind earns its keep.

Features & Capabilities

  • Reliable, hard-to-fool magnetic-contact detection
  • Zoned, located alarms for fast response
  • Arm/disarm states with entry/exit delays
  • End-of-line supervision against tamper (cut/short)
  • Networked instant alerts + local siren
  • Battery backup against power-cut attacks
  • Honest layering with glass-break/motion for full coverage

Difficulty, Time & Required Skills

AttributeValue
Difficulty levelBeginner
Estimated completion time8–14 hours
Indicative build cost₹2,200 – ₹3,800
Primary disciplineSecurity
Reference platformESP32 DevKit V1 (ESP-WROOM-32)

Skills you should have (or will pick up)

  • Wiring magnetic (reed) contacts and multi-zone loops
  • End-of-line supervision to detect cut/short tamper
  • Arm/disarm state machines with entry/exit delays
  • Networked alerting and event logging
  • Battery-backup and power-loss handling

Bill of Materials

Every part below is commonly available from Indian and international hobby-electronics suppliers. Prices are indicative 2026 retail figures in Indian rupees and will drift — treat them as a budgeting guide, not a quotation.

ComponentKey specificationQtyApprox. cost
ESP32 DevKit V1 (ESP-WROOM-32)
Wi-Fi transmit bursts peak near 500 mA — size the regulator accordingly.
Dual-core Xtensa LX6 @ 240 MHz, 520 KB SRAM, 4 MB flash, Wi-Fi 802.11 b/g/n + BLE 4.2, 34 GPIO, 18× 12-bit ADC, 2× 8-bit DAC1₹450
Reed switch + magnet pair (door contact)
Wire it normally-closed so a cut cable reads the same as an open door — fail-safe by construction.
NO contact, 10–15 mm operating gap, 100 V / 0.5 A, > 10⁷ operations6₹330
Active piezo buzzer 5 V
Active buzzers make tone on DC; passive ones need a PWM carrier.
85 dB at 10 cm, 2.3 kHz resonance, 12 mm diameter1₹25
5 V single-channel opto-isolated relay module
Cut the JD-VCC jumper and feed the coil separately for true isolation.
SPDT contacts rated 10 A @ 250 VAC / 10 A @ 30 VDC, opto-isolated input1₹90
0.96″ SSD1306 OLED display
Static images burn in — invert or scroll the screen periodically.
128 × 64 monochrome, 1.3–3.3 V logic, 100 kHz–400 kHz I²C1₹250
4×4 matrix membrane keypad
Debounce in software — 20 ms is enough for membrane keys.
16 keys, 8-wire matrix, 35 × 76 mm adhesive-backed1₹70
18650 Li-ion cell 3400 mAh + holder
Never charge below 0 °C; always use a protected cell or a BMS.
3.7 V nominal, 4.2 V full, 3400 mAh, ~12.6 Wh, 2 C discharge1₹450
Magnetic door/window contacts
Surface or recessed; recessed is harder to defeat
Reed + magnet contact sets, one per opening (zone)8₹3,840
End-of-line resistorsFor supervised loops (detect cut/short)8₹480
Siren + strobeLoud internal/external sounder1₹500
Backup battery + chargerSo a mains cut does not disarm the panel1₹300

Estimated total: ₹6,785, excluding tools, shipping and consumables.

Tools and consumables

  • Soldering iron (temperature controlled, 350 °C) with 0.8 mm 60/40 or lead-free solder
  • Digital multimeter — continuity, DC volts and current ranges
  • Wire strippers, flush cutters and a small set of precision screwdrivers
  • Heat-shrink tubing and a heat gun (or a lighter, carefully)
  • A laptop with a USB port and the toolchain listed above

Hardware Specifications

PartSpecificationSupplyInterfaceReference
ESP32 DevKit V1 (ESP-WROOM-32)Dual-core Xtensa LX6 @ 240 MHz, 520 KB SRAM, 4 MB flash, Wi-Fi 802.11 b/g/n + BLE 4.2, 34 GPIO, 18× 12-bit ADC, 2× 8-bit DAC3.3 V logic / 5 V USBUART, SPI, I²C, I²S, CAN, PWMDatasheet
Reed switch + magnet pair (door contact)NO contact, 10–15 mm operating gap, 100 V / 0.5 A, > 10⁷ operationspassiveDigital with pull-upDatasheet
Active piezo buzzer 5 V85 dB at 10 cm, 2.3 kHz resonance, 12 mm diameter3–5 VDigital / PWMDatasheet
5 V single-channel opto-isolated relay moduleSPDT contacts rated 10 A @ 250 VAC / 10 A @ 30 VDC, opto-isolated input5 V coilDigital (active-low)Datasheet
0.96″ SSD1306 OLED display128 × 64 monochrome, 1.3–3.3 V logic, 100 kHz–400 kHz I²C3.3–5 VI²C (0x3C)Datasheet
4×4 matrix membrane keypad16 keys, 8-wire matrix, 35 × 76 mm adhesive-backedlogic level8 GPIO matrix scanDatasheet
18650 Li-ion cell 3400 mAh + holder3.7 V nominal, 4.2 V full, 3400 mAh, ~12.6 Wh, 2 C discharge3.0–4.2 VHolder / spot-welded tabsDatasheet

Consolidated electrical and interface specifications for every active part in the build.

Power Budget & Supply Sizing

Add up the typical active current of every part, then size the supply with at least 50 % headroom so transmit bursts and motor inrush never brown out the controller.

LoadSupply railTypical current (mA)Notes
ESP32 DevKit V1 (ESP-WROOM-32)3.3 V logic / 5 V USB160Wi-Fi transmit bursts peak near 500 mA — size the regulator accordingly.
Active piezo buzzer 5 V3–5 V30Active buzzers make tone on DC; passive ones need a PWM carrier.
5 V single-channel opto-isolated relay module5 V coil70Cut the JD-VCC jumper and feed the coil separately for true isolation.
0.96″ SSD1306 OLED display3.3–5 V20Static images burn in — invert or scroll the screen periodically.
4×4 matrix membrane keypadlogic level1Debounce in software — 20 ms is enough for membrane keys.

Summed typical draw is 281 mA. With a 1.5× design margin the supply should deliver at least 500 mA continuously at the stated rail voltage.

Software Requirements & Development Environment

Reference toolchain: Arduino IDE 2.3.x with the ESP32 board package 3.x (or PlatformIO on VS Code). Anything newer normally works; anything older may lack the board definitions used here.

  • Install the Arduino IDE 2.3.x (or PlatformIO if you prefer a real editor and dependency locking).
  • Add https://espressif.github.io/arduino-esp32/package_esp32_index.json under File → Preferences → Additional Board Manager URLs, then install esp32 from the Boards Manager.
  • Set the correct port under Tools → Port. On Linux add yourself to the dialout group: sudo usermod -aG dialout $USER and log out and back in.
  • Open the Serial Monitor at 115200 baud — every sketch here logs its state there.
  • Keep File → Preferences → Show verbose output during: compilation switched on while you are debugging build errors.

Required libraries

LibraryWhy it is neededInstall
WiFi (ESP32 core) bundledStation/AP connection management for the ESP32.Bundled with the ESP32 Arduino core
PubSubClient 2.8Lightweight MQTT 3.1.1 client for constrained devices.Library Manager → "PubSubClient" by Nick O'Leary
Adafruit SSD1306 + GFX 2.5.xFramebuffer and text/graphics primitives for the OLED.Library Manager → "Adafruit SSD1306"
NTPClient / configTime bundledWall-clock time from an NTP server for timestamping.Bundled (`configTime()` on ESP32)
Preferences (NVS) bundledWear-levelled key/value storage in ESP32 flash for settings.Bundled with the ESP32 core

Block Diagram

The block diagram shows the functional decomposition of the system — what senses, what decides, what acts, and where the data ends up.

Door/Window Breach Alarm — system block diagramFunctional block diagram of the Door/Window Breach Alarm system. Sense openingsZone contactsreed, supervisedKeypadarm/disarmDecideESP32state machineEntry/exitdelaysRespondSirenlocated alarmSupervisioncut/short faultNotifyPhone/panellocated alertrightrightnone
Door/Window Breach Alarm — system block diagram

Circuit Diagram & Wiring

Every signal line in the build is shown below, followed by a pin-by-pin connection table you can work through with a multimeter in hand.

Door/Window Breach Alarm — wiring schematicConnection schematic showing which controller pin drives each peripheral. Sensors / InputsControllerActuators / OutputsESP32 DevKit V1(ESP-WROOM-32)3.3 V logic / 5 V USBZone loops (supervised)GPIO 34/35/32/33Per-zone contact +supervision (ADC)KeypadGPIOArm/disarm codeTamperGPIO 14Panel tamperSiren relayGPIO 26SounderOLEDGPIO 21/22Status/zone displayWi-FiAlerts + loggingBackup batteryADCPower supervision
Door/Window Breach Alarm — wiring schematic
PeripheralPeripheral pinController pinSignal
Zone loops (supervised)EOLGPIO 34/35/32/33Per-zone contact + supervision (ADC)
KeypadmatrixGPIOArm/disarm code
TamperNCGPIO 14Panel tamper
Siren relayINGPIO 26Sounder
OLEDSDA/SCLGPIO 21/22Status/zone display
Wi-Fion-chipAlerts + logging
Backup batterysenseADCPower supervision

Wire one row at a time and tick it off — most "it does not work" reports trace back to a single swapped pair.

Wiring explanation

  • Wire each zone as a supervised loop with an end-of-line resistor read on an ADC, so a normal closed contact, an open contact, a cut wire and a short each give a distinct level.
  • Use recessed contacts where possible (harder to defeat than surface-mounted) and one contact per opening for located zones.
  • Power the panel from mains with automatic battery backup and sense the supply so power loss is detected and alerted.
  • Add a panel tamper switch, and drive the siren via a relay sized for it.
  • Keep interior zones separate from perimeter zones so armed-home can watch the perimeter while allowing interior movement.
A wall-mounted CCTV surveillance camera
ESP32 panel reads each supervised zone and runs the armed-state machine with entry/exit delays. Photograph sourced from Wikimedia Commons — CCTV camera.jpg. Reused under the licence stated on that page; please check it before republishing.

System Architecture

Read the stack from the bottom up: physical hardware, the firmware that drives it, the transport that moves data off the device, and the software a human actually looks at.

Door/Window Breach Alarm — architecture stackLayered architecture from hardware to user interface. Hardware layerESP32 DevKit V1 (ESP-WROOM-32) · sensors and actuatorsDriver layerwifi · pubsub · ssd1306 · ntpApplication logicsampling loop · filtering · thresholds · state machineTransport layerWi-Fi + MQTT → phone / alarm panel · TLS · retry and backoffPresentation layerdashboard · mobile notifications · historical charts
Door/Window Breach Alarm — architecture stack

Working Principle

A magnetic contact is the archetypal intrusion sensor because it answers a binary, physical question with almost no ambiguity: is this opening open or shut? A magnet holds a reed switch closed while the door is shut; opening the door separates them and the switch opens. There is little to false-trigger on — no light, temperature or vibration to confuse it — which is why contacts are trusted as the primary perimeter sensor. The engineering that makes a system out of them is about three things the bare switch does not provide: knowing where, controlling when it is active, and detecting attempts to defeat it.

Zoning gives the "where". Each contact is wired and tracked as its own zone, so an alarm is not just "something happened" but "the kitchen window opened", which is what lets a responder go straight to the breach and lets the owner tell a real event from a mistake. Zones are also typed — perimeter (doors/windows) versus interior — so the system can behave differently in different armed states.

Arm/disarm with entry/exit delays gives the "when". A real alarm is not always on; it has states. Disarmed, openings are merely logged. Armed-away, every perimeter and interior zone is active. Armed-home, the perimeter is watched but interior zones are ignored so occupants can move around. And because you must be able to leave and enter through a door without tripping the alarm, a designated entry/exit zone gets a delay: an exit delay after arming to get out, and an entry delay on opening that door to reach the keypad and disarm — while any other zone opening alarms instantly, since an intruder coming through a window has no legitimate delay. This state machine is what makes an alarm livable rather than a constant nuisance.

The "defeat" problem is where amateur systems fail and supervision earns its place. The obvious way to beat a contact is to stop the loop from ever seeing the door open — bypass the switch with a wire (a short), or cut the wire so the panel simply loses the sensor. A naive two-state input (open/closed) cannot tell a healthy closed contact from a shorted-out one, or a cut wire from a benign disconnect, so it can be silently disabled. The classic fix is an end-of-line resistor: a resistor at the far end of the loop so that a healthy closed contact reads one specific resistance, an open contact another, a short reads zero and a cut reads infinite — four distinguishable states from one wire. Now cutting or shorting a zone is a detected tamper/fault, alarmed like any breach, so a defeated sensor is never a silent blind spot. Coupled with battery backup (so cutting mains does not disarm the panel, and power loss is itself alerted) and supervision of wireless contacts' batteries, this is what makes the perimeter trustworthy. The system is candid that contacts detect opening and not glass being smashed or a wall breached — which is why complete installations layer glass-break and motion sensors on top — and that a homebrew panel is not a professionally-monitored alarm. But as a fast, located, supervised, networked perimeter alarm, it reliably catches the most common intrusion of all: a door or window opened that should have stayed shut.

The maths behind it

End-of-line supervised zone

plainEnd-of-line supervised zone
One EOL resistor per loop gives four distinguishable states
from the measured resistance/voltage:

  R ≈ R_eol      → closed & healthy (normal)
  R ≈ R_eol+R_x  → contact OPEN (breach)   [with series/parallel scheme]
  R ≈ 0          → SHORT (tamper/bypass)
  R ≈ ∞          → OPEN CIRCUIT (cut wire/tamper)

Cut and short are alarmed — a defeated zone is never silent.

Arm-state + entry/exit logic

plainArm-state + entry/exit logic
On a monitored zone opening while ARMED:

  if zone == ENTRY_DELAY_ZONE: start entry timer T_e
     alarm if not disarmed within T_e
  else: alarm immediately (located)

ARMED_HOME ignores INTERIOR zones; ARMED_AWAY includes them.
Exit timer T_x after arming lets you leave without a trip.

Power-loss supervision

plainPower-loss supervision
Battery backup keeps the panel armed; watch the mains:

  if mains_lost: log + alert "power loss" (stay armed)
  if battery_low: maintenance alert before it fails

Cutting power must not disarm — and should itself warn.

Program Flowchart

The firmware is a single cooperative loop. Nothing blocks for long, so networking, sensing and the user interface all stay responsive.

Door/Window Breach Alarm — firmware flowchartControl flow through the main program loop. Read supervised zonesLoop fault (cut/short)?Zone fault alarmCheck stateZone fault alarmCheck stateArmed and a monitored zoneopened?Entry delay then alarm (located)Log open/closeEntry delay then alarm(located)Log open/closeSiren + notify; await disarm
Door/Window Breach Alarm — firmware flowchart

Assembly Instructions

Build on a breadboard first and only commit to solder once the whole system has run for an hour without a fault.

  1. Fit supervised contacts on every opening

    Mount a magnetic contact on each protected door and window (recessed where possible), wiring each as a supervised loop with an end-of-line resistor so cut/short is detectable.

    Assign each contact a zone name and type (perimeter/interior, and which is the entry/exit door).

  2. Wire the panel with backup power

    Connect the zone loops, keypad, siren relay and OLED to the ESP32 panel, powered from mains with automatic battery backup and mains-sensing so power loss is detected.

  3. Set up alerts and states

    Configure Wi-Fi alerts/logging, and the armed-away/armed-home/disarmed states with the entry/exit door and delays.

Step-by-Step Implementation Guide

Work through these in order. Each step ends in something you can observe, so a failure is always localised to the step you just finished.

  1. Read supervised zones into four states

    Measure each loop and classify it as normal, open (breach), short (tamper) or cut (tamper), so both breaches and defeat attempts are detected.

    cppsupervised-zone.ino
    enum ZoneState { NORMAL, OPEN, SHORT, CUT };
    
    // One EOL-resistor loop read on an ADC → four distinguishable states.
    ZoneState readZone(int pin) {
      int v = analogRead(pin);              // 0..4095
      if (v < 100)   return SHORT;          // near 0 -> shorted (tamper)
      if (v > 3900)  return CUT;            // near full -> open circuit (cut)
      if (v > 1600 && v < 2400) return NORMAL;   // EOL band -> closed & healthy
      return OPEN;                          // out of the EOL band -> contact open
    }
    
    // Classify all zones; return true if any needs to alarm given the state.
    bool scanZones(ZoneState st[], int n, ArmState arm) {
      bool alarm = false;
      for (int z = 0; z < n; z++) {
        st[z] = readZone(zonePin[z]);
        if (st[z] == SHORT || st[z] == CUT) { raise(z, "tamper"); alarm = true; }
        else if (st[z] == OPEN && zoneActive(z, arm)) {
          raise(z, "breach"); alarm = true;         // located, arm-aware
        }
        logState(z, st[z]);
      }
      return alarm;
    }
    if (v < 100) return SHORT;A near-zero reading means the loop has been shorted — a bypass attempt — which is treated as tamper, not a benign closed contact.
    if (v > 3900) return CUT;A near-full reading means the wire is cut/open — the sensor has been removed — again tamper, not silently ignored.
    if (v > 1600 && v < 2400) return NORMAL;Only a reading in the end-of-line resistor's band counts as a healthy closed contact; anything else is a meaningful, distinguishable state.
    if (st[z] == SHORT || st[z] == CUT) { raise(z, "tamper")Cut and short are alarmed like a breach, so a defeated zone can never become a silent blind spot.
    else if (st[z] == OPEN && zoneActive(z, arm))A contact opening alarms only if that zone is active in the current armed state, giving located, arm-aware breach detection.
  2. Run the arm-state machine and alert

    Apply the armed-away/armed-home rules and entry/exit delays, sound the siren and push a located alert on an alarm, and log every open/close and state change.

Complete Source Code

The listing below is complete and compiles as written — there are no elided sections. Read the annotations under each block before you upload it.

cppbreach-alarm.ino
/* ═══════════════════════════════════════════════════════════════
   Door/Window Breach Alarm — ESP32, supervised multi-zone

   Magnetic contacts on doors/windows, each a supervised (EOL) zone,
   with armed-away/home/disarmed states and entry/exit delays. Cut/short
   tamper is alarmed. Located alerts + local siren; battery backup.
   ══════════════════════════════════════════════════════════════════ */

#include <WiFi.h>
#include <PubSubClient.h>
#include <Wire.h>
#include <Adafruit_SSD1306.h>
#include <Preferences.h>

#define PIN_SIREN 26
#define PIN_MAINS 15
#define NZONES     6
#define ENTRY_ZONE 0        // the entry/exit door
#define ENTRY_MS 30000UL
#define EXIT_MS  30000UL

const int zonePin[NZONES] = {34,35,32,33,25,27};
const bool interior[NZONES] = {false,false,false,false,true,true};
const char *zoneName[NZONES] =
  {"Front door","Kitchen win","Bedroom win","Back door","Hall PIR","Landing PIR"};

enum ArmState { DISARMED, ARMED_HOME, ARMED_AWAY };
enum ZoneState { NORMAL, OPEN, SHORT, CUT };
ArmState arm = DISARMED;
Adafruit_SSD1306 oled(128,64,&Wire);
Preferences prefs;
WiFiClient net; PubSubClient mqtt(net);
uint32_t entryStart = 0; bool entryRunning = false, alarming = false;

ZoneState readZone(int pin) {
  int v = analogRead(pin);
  if (v < 100) return SHORT;
  if (v > 3900) return CUT;
  if (v > 1600 && v < 2400) return NORMAL;
  return OPEN;
}
bool zoneActive(int z) {
  if (arm == DISARMED) return false;
  if (arm == ARMED_HOME && interior[z]) return false;  // allow interior at home
  return true;
}

void alertLocated(int z, const char *why) {
  char m[120];
  snprintf(m,sizeof m,"{\"zone\":%d,\"name\":\"%s\",\"event\":\"%s\"}",
           z+1, zoneName[z], why);
  mqtt.publish("alarm/event", m);
}
void triggerAlarm(int z, const char *why) {
  alarming = true;
  digitalWrite(PIN_SIREN, HIGH);
  alertLocated(z, why);
}

void setup() {
  Serial.begin(115200);
  pinMode(PIN_SIREN, OUTPUT);
  pinMode(PIN_MAINS, INPUT);
  for (int z=0; z<NZONES; z++) analogSetPinAttenuation(zonePin[z], ADC_11db);
  Wire.begin(21,22); oled.begin(SSD1306_SWITCHCAPVCC, 0x3C);
  WiFi.begin(WIFI_SSID, WIFI_PASS);
  mqtt.setServer(MQTT_HOST, 1883);
  prefs.begin("alarm", true);
  arm = (ArmState)prefs.getInt("arm", DISARMED);
  prefs.end();
}

void loop() {
  if (!mqtt.connected() && WiFi.status()==WL_CONNECTED) mqtt.connect("alarm-1");
  mqtt.loop();
  handleKeypad(&arm);                      // arm/disarm; sets exit delay etc.

  if (digitalRead(PIN_MAINS) == LOW)       // power-loss supervision
    mqtt.publish("alarm/status", "power loss (armed on battery)");

  oled.clearDisplay(); oled.setCursor(0,0);
  for (int z = 0; z < NZONES; z++) {
    ZoneState s = readZone(zonePin[z]);
    if (s == SHORT || s == CUT) triggerAlarm(z, "tamper");
    else if (s == OPEN && zoneActive(z)) {
      if (z == ENTRY_ZONE && !entryRunning) {   // entry delay on the door
        entryRunning = true; entryStart = millis();
        mqtt.publish("alarm/status", "entry delay");
      } else if (z != ENTRY_ZONE) {
        triggerAlarm(z, "breach");               // other zones: instant
      }
    }
    oled.printf("%s\n", zoneName[z]);
  }
  oled.display();

  if (entryRunning && millis() - entryStart > ENTRY_MS && arm != DISARMED)
    triggerAlarm(ENTRY_ZONE, "breach (entry timeout)");
  if (arm == DISARMED) { entryRunning = false; alarming = false;
                         digitalWrite(PIN_SIREN, LOW); }
  delay(50);
}
ZoneState readZone(int pin)Each supervised loop is resolved into four states — normal, open, short, cut — so both a real opening and a wire-cut/short bypass are detectable from one input.
bool zoneActive(int z)Whether a zone can alarm depends on the armed state, so armed-home ignores interior zones while still watching every perimeter opening.
if (s == SHORT || s == CUT) triggerAlarm(z, "tamper")Cut and short loops alarm as tamper, ensuring a defeated zone is never a silent gap in the perimeter.
if (z == ENTRY_ZONE && !entryRunning)Opening the designated entry door starts an entry delay to reach the keypad, while any other zone alarms instantly — an intruder through a window gets no grace.
if (digitalRead(PIN_MAINS) == LOW)Loss of mains is reported while the panel keeps running on battery, so a power-cut attack is both survived and announced.

Configuration & Calibration

Configuration steps

  • Define each zone's name, type (perimeter/interior) and the entry/exit door and delays.
  • Set the end-of-line thresholds for your resistor values so normal/open/short/cut are distinct.
  • Configure the armed states, keypad codes, siren and Wi-Fi alerts/logging.
  • Ensure mains-sensing and battery backup with a low-battery maintenance alert.

Calibration procedure

An uncalibrated sensor produces confident, precise, wrong numbers. Do this once per physical unit and record the constants.

  1. EOL thresholds

    Measure each zone's ADC value for closed, open, shorted and cut conditions and set the bands so all four are cleanly distinguished.

  2. Delays

    Set exit/entry delays long enough to leave/reach the keypad but no longer, and confirm non-entry zones alarm instantly.

  3. Power backup

    Verify the panel stays armed and reports power loss on a simulated mains cut, and set the low-battery threshold.

Network Architecture & Connectivity

Door/Window Breach Alarm — network topologyPath taken by telemetry from field node to end user. Edge nodesGatewayCloudClientsAlarm panelESP32ZonescontactsWi-Fi 2.4 GHzRouterlocal siren tooMQTT 1883App / panelzone state + alarmsPhonelocated alertsPanelarm/disarm
Door/Window Breach Alarm — network topology

Communication protocol

Zone states, arm changes and alarms publish immediately; the local siren sounds independent of the network so connectivity loss cannot silence a breach.

Topic / endpointDirectionPayload
alarm/eventpanel → applocated breach / tamper (zone, name, event)
alarm/statuspanel → apparmed state, entry delay, power loss
alarm/cmdapp → panelarm/disarm, acknowledge

Message contract between the device and the broker.

Cloud platform configuration

An app/panel shows zone states and armed mode, logs open/close and alarm history, and pushes instant located alerts; multiple panels cover larger premises.

Dashboard setup

A floor plan of zones coloured by state, armed-mode control, and an event log of breaches, tampers and arm/disarm actions.

Mobile app integration

Instant located breach/tamper alerts and power-loss/low-battery maintenance alerts, with remote arm/disarm.

Security considerations

  • Authenticate arm/disarm commands; keep the local siren and supervision independent of the network.
  • Supervise loops (EOL) and power so tamper and power-cut are alarmed, not silent.
  • Alert on panel tamper and connectivity loss.

Testing Procedure & Expected Output

Test from the bottom up. Confirm power, then each sensor in isolation, then the integrated loop — the first failing step tells you exactly where to look.

TestWhat you should see
Open a perimeter window while armedInstant located alarm for that zone
Open the entry door while armedEntry delay starts; alarm only if not disarmed in time
Short or cut a zone loopTamper alarm for that zone (not silently disabled)
Armed-home with interior movementInterior zones ignored; perimeter still armed
Cut mains powerStays armed on battery; power-loss alert
Disarm at the keypadSiren clears; system returns to disarmed/logging

Bench-test checklist. If a row fails, stop and fix it before moving on.

Expected output

The app/panel shows each zone's state and the armed mode; a breach or tamper produces an instant located alert and log entry.

jsonalarm-event.json
{
  "zone": 2,
  "name": "Kitchen win",
  "event": "breach",
  "time": "2026-07-27T02:03:18"
}

A located breach on the kitchen window fires the siren and pushes an alert naming the zone; a cut or shorted loop would instead raise a tamper alarm for that zone, so a defeat attempt is never silent.

A city skyline at night
A breach fires a located alert and siren; supervision turns any cut or shorted loop into a tamper alarm. Photograph sourced from Wikimedia Commons — Smart city.jpg. Reused under the licence stated on that page; please check it before republishing.

Troubleshooting: Common Errors & Fixes

Zone can be bypassed silently

Likely cause. Unsupervised loop

Fix. Add end-of-line resistors and detect short/cut as tamper

False alarms on the entry door

Likely cause. No entry delay or delay too short

Fix. Configure the entry/exit zone and a sensible delay

Interior movement trips armed-home

Likely cause. Interior zones not typed/excluded

Fix. Mark interior zones and exclude them in armed-home

Disarmed by pulling power

Likely cause. No battery backup

Fix. Add battery backup; alert on power loss; keep armed on battery

Alarm not located

Likely cause. Zones not individually tracked

Fix. One contact per zone with a name; report the zone in the alert

The sketch will not upload — "Failed to connect" or "avrdude: stk500_recv()"

Likely cause. The bootloader is not being reached: wrong port, wrong board, a serial monitor holding the port open, or a USB cable that only carries power.

Fix. Close every serial monitor, confirm Tools → Board and Port, and swap to a known data-capable USB cable. On an ESP32 hold BOOT while the IDE prints "Connecting…", then release. If a peripheral is wired to the UART pins (GPIO 1/3 on ESP32, D0/D1 on Uno) unplug it — it fights the programmer.

The board resets in a loop, or the serial monitor prints "Brownout detector was triggered"

Likely cause. The supply cannot deliver peak current. Wi-Fi transmit bursts, relay coils and servos all pull far more than their average draw.

Fix. Power peripherals from a separate regulated supply with a common ground rather than from the board 5 V pin. Add a 470–1000 µF electrolytic capacitor across the supply near the load, and use a real power adapter rather than a laptop USB port.

Serial monitor shows garbage characters

Likely cause. Baud rate mismatch between Serial.begin() and the monitor, or a floating/shared UART line.

Fix. Set the monitor to 115200 to match the sketch. If it still garbles, the crystal or the USB bridge is being confused by noise — shorten the cable and keep motor wiring away from the USB lead.

An I²C device is not detected

Likely cause. Wrong address, missing pull-ups, swapped SDA/SCL, or a bus too long for the pull-up value.

Fix. Run an I²C scanner sketch first — it should print the device address. Most breakout boards include 4.7 kΩ pull-ups, but if you have chained four of them the parallel resistance is too low; remove the pull-ups from all but one board. Keep the bus under 30 cm at 100 kHz.

Wi-Fi connects but MQTT never does (state -2)

Likely cause. Wrong broker address or port, a firewall in the way, or the broker requiring credentials the sketch is not sending.

Fix. Test from a laptop on the same network first: mosquitto_sub -h <broker> -t "#" -v. If that works, the problem is on the device — check the IP literal, port 1883 (or 8883 for TLS), and that client.setServer() runs before connect(). PubSubClient state codes are documented in its header.

Readings arrive for a while and then stop

Likely cause. The Wi-Fi or MQTT session dropped and the sketch never reconnects, or the broker dropped the client on keep-alive timeout.

Fix. Never assume the link stays up. Check WiFi.status() and client.connected() at the top of every loop and reconnect with exponential backoff. Add a watchdog so a wedged network stack reboots the device instead of going silent.

Performance Optimisation

  • Poll zones fast enough to catch an opening instantly while keeping ADC work light.
  • Debounce contacts to avoid chatter without adding perceptible delay.
  • Keep the local siren and supervision independent of the network; alert immediately.
  • Log open/close and state changes for a useful audit without flooding.
  • Replace every delay() with a millis() comparison — blocking delays are the single most common cause of dropped readings.
  • Sample sensors on a fixed cadence and publish on a slower one; you almost never need to transmit at the sampling rate.
  • Move networking into its own FreeRTOS task so a slow DNS lookup cannot stall the control loop.
  • Use uint8_t / uint16_t where the range allows; on an 8-bit AVR a 32-bit add costs four times as much.
  • Batch several samples into one MQTT publish. Radio time, not CPU time, dominates the energy budget.
  • Set the MQTT keep-alive to a value that matches your reporting interval so the broker does not churn reconnections.
  • For battery builds use deep sleep between samples: an ESP32 drops from ~160 mA awake to about 10 µA asleep, which is the difference between days and months of runtime.

Safety Precautions

  • Contacts detect opening, not glass-break or wall breach — layer with glass-break/motion for full coverage.
  • Supervise loops so a defeated sensor is an alarm, and provide battery backup against power-cut attacks.
  • A DIY alarm is not a professionally-monitored system; use one where required, and never rely on it alone for life-safety.
  • Ensure sirens and alerts comply with local noise and alarm regulations.
  • Mains voltage kills. Anything on the load side of the relay is at 230 V. Do not work on a powered circuit, and never leave exposed mains wiring on a bench where someone could touch it.
  • Keep at least 6 mm of creepage between the mains and low-voltage sides of any board you make, and never route mains tracks under the microcontroller.
  • Have a qualified electrician do the final installation into a consumer unit or wall fitting. In most jurisdictions this is a legal requirement, not a suggestion.
  • Fit an RCD/RCBO upstream and fuse the load appropriately for its rating.
  • Lithium cells vent and burn when abused. Only use protected cells or a proper BMS, never charge below 0 °C, and never leave a charging pack unattended on a wooden desk.
  • Wear eye protection when soldering or cutting, and solder in a ventilated space — rosin flux fumes are a respiratory irritant.
  • Power the circuit through a bench supply with a current limit while you are testing. A 300 mA limit turns a wiring mistake into a beep instead of a dead board.
  • Disconnect power before changing any wiring. Hot-plugging a sensor onto a live bus is the fastest way to lose a controller.

Maintenance

  • Test each zone (open, and cut/short) periodically to confirm detection and supervision.
  • Check magnet alignment and replace weak wireless-contact batteries (supervised).
  • Verify battery backup run-time and the low-battery/power-loss alerts.
  • Keep zone names/floor plan accurate as the premises change.
  • Re-check every screw terminal and header after the first week — thermal cycling loosens connections that felt tight on day one.
  • Log pack voltage. When resting voltage after a full charge drops below about 4.0 V, the cell is near end of life — replace it.
  • Keep the broker and dashboard containers patched, and rotate device credentials at least once a year.
  • Recalibrate at the interval given in the calibration section, and keep the constants in a text file next to the firmware — not only in flash.
  • Keep a short logbook of firmware versions and what changed. Six months later you will not remember why that constant is 1.083.

Future Improvements & Upgrades

A working v1 is a platform, not a finish line. These are the upgrades that add the most capability for the least rework.

  • Add glass-break and motion (PIR) sensors as additional zone types for full coverage.
  • Add wireless supervised contacts to simplify retrofits.
  • Integrate cameras so a breach pulls up the relevant view.
  • Add smart-home actions (lights on, doors lock) on alarm.
  • Design a proper PCB. Once the breadboard version has run for a month, moving to a two-layer board removes the intermittent-contact failures that dominate prototype faults.
  • Add over-the-air firmware updates so you never have to physically reach a deployed node again.
  • Add persistent local storage (microSD or the on-chip flash) so a network outage does not create a hole in your data.
  • Move configuration out of the source: a captive-portal setup page or a JSON config file makes the build reusable without a recompile.
  • Add a battery and solar option so the unit survives a power cut and can be sited away from a socket.
  • Write a small test harness that feeds synthetic sensor values through the decision logic, so you can validate thresholds without physically triggering the event.

Frequently Asked Questions

Why supervise the wiring?

Because the easy way to beat a contact is to short or cut its loop. An end-of-line resistor lets the panel tell a healthy closed contact from a shorted or cut one, so a bypass attempt becomes a tamper alarm instead of a silent blind spot.

What are entry/exit delays for?

So you can leave and enter through a designated door without tripping the alarm. That door gets a delay to reach the keypad; every other zone alarms instantly, since an intruder through a window has no legitimate delay.

Does a magnetic contact detect a smashed window?

No — it detects the window being opened. Breaking the glass without opening the sash may not trip it, which is why complete systems add glass-break and motion sensors alongside contacts.

What if the burglar cuts the power?

Battery backup keeps the panel armed and the loss of mains is itself alerted, so cutting power neither disarms the system nor goes unnoticed.

Why zone each opening separately?

So the alarm is located. Knowing "kitchen window" rather than just "alarm" speeds response and helps tell a real breach from a mistake.

References & Learning Resources

These are the primary sources worth reading in full. Manufacturer datasheets always outrank forum posts when the two disagree.

  1. Magnetic (reed) contacts in alarm systemsReference
  2. Burglar alarm zones and supervisionReference
  3. End-of-line resistor supervisionReference
  4. Entry/exit delay and arming statesReference
  5. Layered intrusion detection (contacts + glass-break + PIR)Reference