Community project

ESP32 Smart Safe

Arduino
Photo of ESP32 Smart Safe

Kaloyan Alexandrov

Published October 25, 2026

Build a smart safe that unlocks with a button code sequence. This project combines an Arduino Uno microcontroller with four push buttons for code entry and an SG90 servo motor to control the locking mechanism. The guide includes a complete wiring diagram, parts list, and step-by-step assembly instructions for mounting the electronics and mechanical lock inside an enclosure.

Followers will learn how to implement debounced button input, servo control, and state management to create a functioning electronic lock. The firmware handles code validation, automatic re-locking after a timed unlock period, and serial debugging output to monitor the safe's status.

Wiring diagram

Wiring diagram for ESP32 Smart Safe

Gather all the parts

QtyComponent
1

SG90 Servo

Micro servo motor (SG90)

1

Push Button

Button 1

Momentary push button switch

1

Push Button

Button 2

Momentary push button switch

1

Push Button

Button 3

Momentary push button switch

1

Push Button

Button 4

Momentary push button switch

Assemble it in 4 steps

1. Поставете платката и бутоните

Поставете Arduino Uno и четирите бутона върху макетна платка. При бутон с четири крачета използвайте две срещуположни крачета, а не две от една и съща страна. Подредете бутоните отляво надясно и ги означете с 1, 2, 3 и 4.

  • Завъртете всеки бутон така, че да пресича средния улей на макетната платка; така двете му страни няма да се окажат постоянно свързани.

2. Свържете бутоните

Свържете по едно краче от всеки бутон към GND на Arduino с черни проводници. Свържете другото краче на бутон 1 към D2, бутон 2 към D3, бутон 3 към D4 и бутон 4 към D5 с различни цветни проводници. Това са съответно сигналите за цифрите от кода.

  • Бутоните не се нуждаят от отделни резистори; програмата използва вградената защита на Arduino.
  • Бутон 1: GND → GND (ground), SIGNAL → D2 (signal). Бутон 2: GND → GND (ground), SIGNAL → D3 (signal). Бутон 3: GND → GND (ground), SIGNAL → D4 (signal). Бутон 4: GND → GND (ground), SIGNAL → D5 (signal).

3. Свържете моторчето за резето

На SG90 обикновено кафявият или черният проводник е GND, червеният е 5 V, а оранжевият или жълтият е сигнал. Свържете кафявия/черния към GND, червения към 5V и оранжевия/жълтия към D9 на Arduino. Поставете пластмасовото рамо на серво моторчето така, че при завъртане да влиза зад ръба на капака като резе.

  • VCC → 5V (power), GND → GND (ground), SIGNAL → D9 (signal).
  • Преди да закрепите рамото окончателно, включете захранването: при стартиране моторчето отива в заключено положение.
  • Не насилвайте рамото на серво моторчето, когато е включено — това може да повреди зъбните колела.
  • Ако Arduino се рестартира или USB се изключва при движение на моторчето, моторчето иска повече ток; спрете и използвайте отделно стабилно 5 V захранване за него, като GND остава обща с Arduino.

4. Поставете механичното резе в кутията

Направете малък отвор за оста на сервото в стената на кутията. Закрепете моторчето здраво с винтчета, двойнозалепваща лента или скоба, после настройте рамото така, че да освобождава капака при отключване и да го застопорява при заключване.

  • Първо пробвайте движението с отворена кутия, за да не се заключи капакът накриво.
  • Това е учебен сейф за малки предмети; пластмасовото серво не е защита срещу насилване.

Review all connections

1. Connections between "servo_lock" and "Arduino"

Functionservo_lockArduino
powerVCC → 5 V power for the servo5V
groundGND → common groundGND
pwmSIGNAL → servo lock-control signalGPIO 9

2. Connections between "button_1" and "Arduino"

Functionbutton_1Arduino
groundGND → common groundGND
digitalSIGNAL → code digit 1GPIO 2

3. Connections between "button_2" and "Arduino"

Functionbutton_2Arduino
groundGND → common groundGND
digitalSIGNAL → code digit 2GPIO 3

4. Connections between "button_3" and "Arduino"

Functionbutton_3Arduino
groundGND → common groundGND
digitalSIGNAL → code digit 3GPIO 4

5. Connections between "button_4" and "Arduino"

Functionbutton_4Arduino
groundGND → common groundGND
digitalSIGNAL → code digit 4GPIO 5

Deploy the firmware

#include <Arduino.h>
#include <Servo.h>

constexpr uint8_t SERVO_PIN = 9;
constexpr uint8_t BUTTON_PINS[] = {2, 3, 4, 5};
constexpr uint8_t CODE[] = {0, 1, 2, 3};  // Buttons 1, 2, 3, 4
constexpr uint8_t CODE_LENGTH = sizeof(CODE);
constexpr int LOCKED_ANGLE = 10;
constexpr int UNLOCKED_ANGLE = 95;
constexpr unsigned long DEBOUNCE_MS = 35;
constexpr unsigned long UNLOCK_TIME_MS = 5000;

Servo lockServo;
bool lastReading[4] = {HIGH, HIGH, HIGH, HIGH};
bool stableState[4] = {HIGH, HIGH, HIGH, HIGH};
unsigned long changedAt[4] = {0, 0, 0, 0};
uint8_t enteredCount = 0;
bool unlocked = false;
unsigned long unlockStartedAt = 0;

void lockSafe() {
  lockServo.write(LOCKED_ANGLE);
  unlocked = false;
  enteredCount = 0;
  Serial.println(F("Locked"));
}

void unlockSafe() {
  lockServo.write(UNLOCKED_ANGLE);
  unlocked = true;
  unlockStartedAt = millis();
  enteredCount = 0;
  Serial.println(F("Unlocked for 5 seconds"));
}

void handleButtonPress(uint8_t button) {
  if (unlocked) {
    return;
  }

  if (button == CODE[enteredCount]) {
    enteredCount++;
    Serial.println(F("Correct button"));
    if (enteredCount == CODE_LENGTH) {
      unlockSafe();
    }
  } else {
    enteredCount = (button == CODE[0]) ? 1 : 0;
    Serial.println(F("Wrong button - code reset"));
  }
}

void setup() {
  Serial.begin(9600);
  for (uint8_t i = 0; i < 4; ++i) {
    pinMode(BUTTON_PINS[i], INPUT_PULLUP);
    lastReading[i] = digitalRead(BUTTON_PINS[i]);
    stableState[i] = lastReading[i];
  }

  lockServo.attach(SERVO_PIN);
  lockSafe();
  Serial.println(F("Safe ready. Enter 1-2-3-4."));
}

void loop() {
  const unsigned long now = millis();

  if (unlocked && now - unlockStartedAt >= UNLOCK_TIME_MS) {
    lockSafe();
  }

  for (uint8_t i = 0; i < 4; ++i) {
    const bool reading = digitalRead(BUTTON_PINS[i]);

    if (reading != lastReading[i]) {
      changedAt[i] = now;
      lastReading[i] = reading;
    }

    if ((now - changedAt[i] >= DEBOUNCE_MS) && reading != stableState[i]) {
      stableState[i] = reading;
      if (stableState[i] == LOW) {
        handleButtonPress(i);
      }
    }
  }

  delay(1);
}

Remix this project

Make it yours in one click

Open a full copy of this project in your own Schematik workspace — diagram, code, parts, and assembly steps included. Swap the sensor, add features, or redesign the whole thing with AI. The author's original stays untouched.

Open in Schematik