A door that nags
A door sensor that counts openings and complains when one stays open. The change has to hold for 50 ms before it counts, so a rattling door or a bouncing contact counts once; after that the sketch prints a reminder every 10 seconds until the door closes. The pull-down in setup() is the same as in the first read.
One opening, several edges
A door does not open in one clean step. The contacts in the sensor can flicker as the magnet leaves, a door in a loose frame rattles, and a wire that is not quite tight makes and breaks. A loop that acts on every change counts all of them.
The sketch waits until a new reading has stayed the same for 50 ms, believes it, and only then counts. A real door changes state and stays changed for far longer than that.
How the sketch debounces
raw is the last thing read, and rawSince is when it last changed. Every
bounce resets rawSince. steady changes only when raw has been
different from it for DEBOUNCE_MS, which a bounce never is. So one real
change is one change of steady, and the count and the timing hang off
that.
There is no delay() in loop(). The debounce is a comparison with
millis(), so the loop keeps reading thousands of times a second and
nothing is missed while it waits.
How it nags
When steady goes LOW the sketch notes the time: the opening and its count.
Each pass of loop() then asks whether the door is still open and
OPEN_TOO_LONG_S has gone by since the last reminder. If so it prints how
long the door has been open, and starts counting again from that moment. So a
door left open for a minute prints about six reminders, ten seconds apart.
Ten seconds is short on purpose, so it can be tried at a desk. For a real
door, raise OPEN_TOO_LONG_S to what that door needs.
What you should see
At 115200 the serial monitor prints the state once at the start, then
door opened (opening 1) when the magnet leaves. Ten seconds later it prints
door has been open for 10 s, and again every ten seconds until the magnet
comes back, when it prints door closed. Each opening counts once, however
fast you move the magnet.
What it does not do
It nags into the serial monitor, which is the nearest thing a bare board has to a voice. A buzzer on another pin, a push to your phone and a message are each another block, and each starts with this condition: the door has been open for more than so many seconds.
The counter lives in RAM, so a restart resets it to zero. On a battery project, a closed sensor draws its small current all day, and deep sleep and the pins that wake it is the place to start.
The code
Reads SIGNAL on every pass of loop(), believes a change only once it has held for DEBOUNCE_MS, then counts openings and nags when one has lasted OPEN_TOO_LONG_S. No delay() anywhere, so nothing is missed while it waits.
/*
Door Sensor - a door that nags TK107 / /p/tk107
Wiring, the same as the first read. Count from the square pad on
the TinkerBlock board, parts up, header at the bottom:
GND -> GND
3V3 -> 3V3 on an ESP32, ESP32-S3 or Pico; 5V on an Uno.
Your board's logic voltage: this pin powers nothing,
it is the level a closed sensor puts on SIGNAL.
NC -> nothing (unconnected on the board)
SIGNAL -> GPIO 4 on an ESP32-S3, GPIO 25 on an ESP32,
D2 on an Uno, GP15 on a Raspberry Pi Pico
The sensor: its two wires go under the two screws of the terminal
block. Either way round; the sensor is a plain switch.
What it does: counts openings, and once the door has been open
longer than OPEN_TOO_LONG_S it prints a reminder every
OPEN_TOO_LONG_S seconds until the door closes. No delay(), so
nothing else is blocked while it waits.
Arduino IDE
Tools > Board your board, e.g. ESP32S3 Dev Module
Tools > Port the one that appears when you plug in
Tools > USB CDC On Boot Enabled (ESP32-S3 only)
No library needed.
*/
// The pin SIGNAL is wired to.
// Uno: 2. ESP32: 25. ESP32-S3: 4. Pico: 15.
const int DOOR_PIN = 4;
// A change counts once it has held this long: longer than a contact
// bounces, shorter than a real door.
const unsigned long DEBOUNCE_MS = 50;
// Open for longer than this and it nags, then nags again this often.
const unsigned long OPEN_TOO_LONG_S = 10;
int raw; // the latest reading
int steady; // the debounced state: HIGH = closed
unsigned long rawSince; // when raw last changed
unsigned long openedAt; // when the door was last seen to open
unsigned long lastNag; // when it last nagged
unsigned int openings = 0;
void setup() {
Serial.begin(115200);
// The board has no pull-down and no pull-up. While the sensor is
// open, SIGNAL is connected to nothing and floats; a closed sensor
// puts VCC on it. So the pin must pull itself LOW: INPUT_PULLDOWN.
// INPUT_PULLUP would be wrong: open would read HIGH, closed would
// read HIGH, and the two could never be told apart.
#ifdef INPUT_PULLDOWN
pinMode(DOOR_PIN, INPUT_PULLDOWN);
#else
// An Uno or Nano has no internal pull-down. Fit a 10 kOhm resistor
// from SIGNAL to GND, or this pin will read noise while open.
pinMode(DOOR_PIN, INPUT);
#endif
// Report the state at power-up, so the first line is never a change.
raw = digitalRead(DOOR_PIN);
steady = raw;
rawSince = millis();
openedAt = millis();
lastNag = millis();
Serial.println(steady == HIGH ? "door closed (magnet near)"
: "door open");
}
void loop() {
unsigned long now = millis();
int level = digitalRead(DOOR_PIN);
if (level != raw) { // a bounce, or a real change starting
raw = level;
rawSince = now;
}
// Believe a change only once it has held for DEBOUNCE_MS.
if (raw != steady && now - rawSince >= DEBOUNCE_MS) {
steady = raw;
if (steady == HIGH) {
Serial.println("door closed");
} else {
openings++;
openedAt = now;
lastNag = now;
Serial.print("door opened (opening ");
Serial.print(openings);
Serial.println(")");
}
}
// Still open after OPEN_TOO_LONG_S: say so, then again every
// OPEN_TOO_LONG_S seconds.
if (steady == LOW && now - lastNag >= OPEN_TOO_LONG_S * 1000UL) {
lastNag = now;
Serial.print("door has been open for ");
Serial.print((now - openedAt) / 1000UL);
Serial.println(" s");
}
}The pull-down is the same as the first read's. With the magnet on the door the sensor is closed, HIGH, while the door is shut, so an opening is a change to LOW. Mounted the other way round, swap the two branches.
View on GitHub · blocks/tk107-door-sensor/arduino/door_open_alarm/door_open_alarm.ino @ v1.13The same alarm in MicroPython, for an ESP32, an ESP32-S3 or a Pico. The debounce and the nag are comparisons of time.ticks_ms values; the loop only sleeps 10 ms between reads.
"""
Door Sensor - a door that nags, MicroPython TK107 / /p/tk107
Wiring, the same as the first read. Count from the square pad on
the TinkerBlock board, parts up, header at the bottom:
GND -> GND
3V3 -> 3V3 (your board's logic voltage: this pin powers
nothing, it is the level a closed sensor puts
on SIGNAL)
NC -> nothing (unconnected on the board)
SIGNAL -> GPIO 4 on an ESP32-S3, GPIO 25 on an ESP32,
GP15 on a Raspberry Pi Pico
The sensor: its two wires go under the two screws of the terminal
block. Either way round; the sensor is a plain switch.
What it does: counts openings, and once the door has been open
longer than OPEN_TOO_LONG_S it prints a reminder every
OPEN_TOO_LONG_S seconds until the door closes.
Thonny
Run > Configure interpreter MicroPython (ESP32) or
MicroPython (Raspberry Pi Pico)
Save it to the board as main.py to run it on every power-up.
"""
from machine import Pin
import time
# ESP32: 25. ESP32-S3: 4. Pico: 15.
DOOR_PIN = 4
# A change counts once it has held this long: longer than a contact
# bounces, shorter than a real door.
DEBOUNCE_MS = 50
# Open for longer than this and it nags, then nags again this often.
OPEN_TOO_LONG_S = 10
# The board has no pull-down and no pull-up. While the sensor is
# open, SIGNAL floats; a closed sensor puts VCC on it. So the pin
# must pull itself LOW: Pin.PULL_DOWN. Pin.PULL_UP would make open
# and closed both read 1.
door = Pin(DOOR_PIN, Pin.IN, Pin.PULL_DOWN)
# Report the state at power-up, so the first line is never a change.
raw = door.value()
steady = raw # 1 = closed, 0 = open
raw_since = time.ticks_ms()
opened_at = time.ticks_ms()
last_nag = time.ticks_ms()
openings = 0
print("door closed (magnet near)" if steady else "door open")
while True:
now = time.ticks_ms()
level = door.value()
if level != raw: # a bounce, or a real change starting
raw = level
raw_since = now
# Believe a change only once it has held for DEBOUNCE_MS.
if raw != steady and time.ticks_diff(now, raw_since) >= DEBOUNCE_MS:
steady = raw
if steady:
print("door closed")
else:
openings += 1
opened_at = now
last_nag = now
print(f"door opened (opening {openings})")
# Still open after OPEN_TOO_LONG_S: say so, then again every
# OPEN_TOO_LONG_S seconds.
if not steady and time.ticks_diff(now, last_nag) >= OPEN_TOO_LONG_S * 1000:
last_nag = now
secs = time.ticks_diff(now, opened_at) // 1000
print(f"door has been open for {secs} s")
time.sleep_ms(10)There is no Uno here: an Uno cannot run MicroPython. The pull-down is Pin.PULL_DOWN. Stop it with Ctrl-C.
View on GitHub · blocks/tk107-door-sensor/micropython/door_open_alarm.py @ v1.13When it does not work
The sketch is counting bounces, or the door is rattling in its frame. A loop that counts every change sees each flicker. Keep the 50 ms debounce, and do not add a delay() that makes the loop miss the settled level.
OPEN_TOO_LONG_S is 10 seconds so it can be tried at a desk. Raise it to the time that suits the door. The first reminder comes that long after the opening, then again every OPEN_TOO_LONG_S.
Check the monitor prints door opened first. If it never does, the pin still reads HIGH with the door open: the magnet may be too near, or the pull-down missing so the pin floats. The nag only starts once the opening is recorded.
The magnet sits near the edge of its range and the door moves a little in its frame, or a wire is loose. Bring the magnet closer to the sensor's face, and check the wires are firm under both screws.
Edit this page — content/books/door-sensor/a-door-that-nags.mdx
Questions about this product
See what other owners have asked, and read their solutions.
Door Sensor
Loading discussions…
Discuss this article
Ask about this page. The answer stays here, on the page it belongs to, for whoever hits the same wall next.