// Gate Controller — ESP32 + PIR + Pushbutton + Servo
// Uses ESP32Servo.h | Non-blocking timing | Debounced button
#include <ESP32Servo.h>
// ── Pin definitions ────────────────────────────────────────────
#define PIR_PIN 13 // PIR sensor signal pin
#define BUTTON_PIN 27 // Pushbutton (active LOW with INPUT_PULLUP)
#define SERVO_PIN 14 // Servo signal pin
// ── Servo positions ────────────────────────────────────────────
#define SERVO_CLOSED 0 // degrees — gate closed
#define SERVO_OPEN 90 // degrees — gate open
// ── Timing constants ──────────────────────────────────────────
#define GATE_OPEN_MS 10000UL // 10 seconds open duration
#define DEBOUNCE_MS 50UL // 50 ms button debounce window
#define PIR_RESAMPLE_MS 200UL // re-check PIR every 200 ms
// ── Objects ───────────────────────────────────────────────────
Servo gateServo;
// ── State variables ───────────────────────────────────────────
bool gateOpen = false; // is the gate currently open?
unsigned long gateOpenedAt = 0; // millis() when gate last opened/extended
// Button debounce state
bool lastButtonReading = HIGH; // last raw pin read
bool buttonState = HIGH; // confirmed (debounced) state
unsigned long lastDebounceTime = 0;
// PIR previous state (for edge detection)
bool lastPirState = LOW;
// ── Helper: open the gate (or extend its timer) ────────────────
void openGate(const char* reason) {
if (!gateOpen) {
gateServo.write(SERVO_OPEN);
gateOpen = true;
Serial.print("[GATE OPENED] Triggered by: ");
Serial.println(reason);
} else {
Serial.print("[GATE EXTENDED] Triggered by: ");
Serial.println(reason);
}
gateOpenedAt = millis(); // reset / extend the timer
}
// ── Helper: close the gate ─────────────────────────────────────
void closeGate() {
gateServo.write(SERVO_CLOSED);
gateOpen = false;
Serial.println("[GATE CLOSED] Auto-close after timeout.");
}
// ── Setup ──────────────────────────────────────────────────────
void setup() {
Serial.begin(115200);
Serial.println("=== Gate Controller Starting ===");
pinMode(PIR_PIN, INPUT);
pinMode(BUTTON_PIN, INPUT_PULLUP); // internal pull-up — avoids floating input
// Allocate a timer channel and attach servo
ESP32PWM::allocateTimer(0);
gateServo.setPeriodHertz(50); // standard 50 Hz servo
gateServo.attach(SERVO_PIN, 500, 2400); // pulse range µs (typical SG90)
gateServo.write(SERVO_CLOSED); // ensure gate starts closed
Serial.println("Gate initialised — CLOSED.");
Serial.println("Waiting for motion or button press...\n");
}
// ── Main loop ─────────────────────────────────────────────────
void loop() {
unsigned long now = millis();
// ── 1. Read & debounce pushbutton ─────────────────────────
bool reading = digitalRead(BUTTON_PIN);
if (reading != lastButtonReading) {
lastDebounceTime = now; // reset the debounce timer on any change
}
lastButtonReading = reading;
if ((now - lastDebounceTime) >= DEBOUNCE_MS) {
// State has been stable for DEBOUNCE_MS
if (reading != buttonState) {
buttonState = reading;
if (buttonState == LOW) { // active LOW — button pressed
openGate("PUSHBUTTON");
}
}
}
// ── 2. Read PIR (rising-edge only — triggers once per detection) ──
bool pirNow = digitalRead(PIR_PIN);
if (pirNow == HIGH && lastPirState == LOW) {
openGate("PIR MOTION");
}
lastPirState = pirNow;
// ── 3. Auto-close check ───────────────────────────────────
if (gateOpen && (now - gateOpenedAt >= GATE_OPEN_MS)) {
closeGate();
}
}