The Smart Temperature Locker combines an ESP32 microcontroller with environmental sensing and servo-controlled locking to create a temperature-aware storage system. It monitors temperature and humidity with a DHT22 sensor, detects proximity with an HC-SR04 ultrasonic sensor, and displays real-time status on a 16x2 LCD screen. The system can lock or unlock via a servo motor and provides audio feedback through a piezo buzzer.
This guide provides a complete wiring diagram, parts list, and step-by-step assembly instructions for building the locker on a breadboard. The included firmware adds WiFi connectivity with admin authentication, allowing remote status monitoring and control through a web interface. Builders will learn how to integrate multiple sensors, manage separate power rails for 3.3V logic and 5V peripherals using a level shifter, and implement a secure OTP-based access system.
Wiring diagram
Gather all the parts
Assemble it in 6 steps
1. Bố trí quanh breadboard
Đặt breadboard nằm ngang ở giữa, ESP32 bên trái với cổng USB quay ra ngoài. DHT22 và HC-SR04 phía trên, LCD phía trên bên phải. Relay và motor phía phải, servo phía dưới phải. LED, công tắc và buzzer phía dưới. Hai bộ chuyển mức nằm gần LCD và các dây tín hiệu 5V. Đi dây dọc mép, rẽ vuông góc và dùng dây ngắn; cách bố trí này là hướng dẫn lắp, không phải tọa độ 3D được cố định.
2. Tách hai đường nguồn
Rãnh dương trên lấy 3V3 ESP32. Rãnh dương dưới lấy nguồn adapter 5V 2A riêng. Nối các rãnh GND với GND ESP32 và adapter. Kiểm tra rãnh nguồn có bị ngắt ở giữa bằng đồng hồ. Tụ 1000uF nối cực cộng vào 5V, cực trừ vào GND gần servo. Dây cấp motor và servo đi trực tiếp từ nguồn, không dồn dòng qua tiếp điểm breadboard.
- Không nối nguồn 5V riêng với VIN khi ESP32 đang cắm USB. Không nối hai rãnh dương 3.3V và 5V với nhau. Tụ ngược cực có thể hỏng.
3. Đấu hai cảm biến
DHT22 VCC vào 3V3, GND vào GND, DATA vào GPIO4; điện trở 4.7k nối DATA lên 3V3. HC-SR04 VCC vào 5V, GND chung. GPIO25 qua LV1/HV1 của controlLevels đến TRIG. ECHO vào điện trở 1k rồi đến nút GPIO18; điện trở 2k từ nút này xuống GND, tạo khoảng 3.33V khi ECHO là 5V. Chỉ dùng nguồn 5V ổn áp, điện trở 1 phần trăm. Các chân NC của DHT để hở.
4. Đấu chuyển mức và màn hình
Cả hai module BSS138 có LV vào 3V3, HV vào 5V, GND chung. Trên levels, LV1 vào GPIO21, HV1 vào SDA LCD; LV2 vào GPIO22, HV2 vào SCL LCD. LCD cấp 5V. LV3 vào GPIO13, HV3 đến tín hiệu servo. LV4 vào GPIO14, HV4 vào IN relay. Điện trở 10k kéo GPIO14 xuống GND. Kênh còn lại của controlLevels để hở.
- Không nối SDA hoặc SCL có kéo lên 5V trực tiếp vào ESP32.
5. Đấu motor và khóa
Relay DFR0017 kích mức HIGH, VCC vào 5V và GND chung. COM vào 5V, NO vào cực cộng motor, cực trừ motor vào GND. Diode mắc song song motor với đầu có vạch vào cực cộng, đầu không vạch vào cực trừ. NC để hở. Servo cấp 5V và GND chung; thử góc 10 độ khóa và 90 độ mở khi chưa gắn tay đòn.
- Chỉ dùng tải DC 5V cho mô hình
- không đấu điện lưới. Che trục quay và tránh kẹt servo.
6. Đấu báo trạng thái
GPIO23 qua 220 ohm đến anode LED xanh, cathode xuống GND; GPIO19 qua 220 ohm đến anode LED đỏ, cathode xuống GND. Công tắc COM vào GPIO27, A xuống GND, B để hở; gạt nối đất nghĩa là cửa đóng. Buzzer piezo thụ động SIG vào GPIO26, GND xuống GND.
- Hai LED chỉ mô phỏng làm mát và làm ấm
- không có sò Peltier thật. Quạt không làm lạnh dưới nhiệt độ phòng.
Review all connections
1. Connections between "levels" and "ESP32"
2. Connections between "controlLevels" and "ESP32"
3. Connections between "temp" and "ESP32"
4. Connections between "sonar" and "ESP32"
5. Connections between "lock" and "ESP32"
6. Connections between "lcd" and "ESP32"
7. Connections between "relay" and "ESP32"
8. Connections between "supply" and "ESP32"
9. Connections between "bulk" and "ESP32"
10. Connections between "pullup" and "ESP32"
11. Connections between "echoTop" and "ESP32"
12. Connections between "echoBottom" and "ESP32"
13. Connections between "relayPull" and "ESP32"
14. Connections between "door" and "ESP32"
15. Connections between "buzzer" and "ESP32"
16. Connections between "blueR" and "ESP32"
17. Connections between "blue" and "ESP32"
18. Connections between "redR" and "ESP32"
19. Connections between "red" and "ESP32"
20. Connections between "motor" and "ESP32"
21. Connections between "diode" and "ESP32"
Deploy the firmware
// Smart Locker: 3.3V logic with isolated external 5V load rail.
#include <Arduino.h>
#include <WiFi.h>
#include <WebServer.h>
#include <DHT.h>
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#include <ESP32Servo.h>
#include <Preferences.h>
#include <esp_system.h>
#include "locker_ui.h"
DHT sensor(4,DHT22);
LiquidCrystal_I2C lcd(0x27,16,2);
float distanceCm=NAN;
String previousLine1,previousLine2;
Servo latch;
WebServer server(80);
Preferences prefs;
const char* admin="locker-demo-2026";
String state="empty", otp="", category="normal";
float temperature=NAN, humidity=NAN, target=30;
bool fan=false, closed=false, rawClosed=false, lastRaw=false, openingSeen=false;
uint32_t edge=0, sample=0, issued=0, unlockedAt=0, blockedAt=0;
int failures=0;
bool pending=false, blocked=false;
void save(){prefs.putString("state",state); prefs.putString("category",category);}
void reply(int code,String text){server.send(code,"application/json",text);}
bool auth(){if(server.header("X-Admin-Key")==admin)return true;reply(403,"{\"error\":\"admin_key\"}");return false;}
void mint(){char b[7];snprintf(b,sizeof(b),"%06lu",(unsigned long)(100000+esp_random()%900000));otp=b;issued=millis();failures=0;}
void status(){
String j="{\"state\":\""+state+"\",\"category\":\""+category+"\",\"temperature\":";
j+=isnan(temperature)?String("null"):String(temperature,2);
j+=",\"humidity\":"+(isnan(humidity)?String("null"):String(humidity,1));
j+=",\"distanceCm\":"+(isnan(distanceCm)?String("null"):String(distanceCm,1));
// Demo occupancy threshold: calibrate to the actual empty compartment.
j+=",\"packagePresent\":"+(isnan(distanceCm)?String("null"):String(distanceCm<20?"true":"false"));
j+=",\"target\":"+String(target)+",\"fan\":"+(fan?"true":"false")+",\"doorClosed\":"+(closed?"true":"false")+",\"sensorFault\":"+(isnan(temperature)?"true":"false")+"}";
reply(200,j);
}
void setup(){
Serial.begin(115200);pinMode(14,OUTPUT);digitalWrite(14,LOW);pinMode(23,OUTPUT);pinMode(19,OUTPUT);pinMode(26,OUTPUT);pinMode(25,OUTPUT);digitalWrite(25,LOW);pinMode(18,INPUT);pinMode(27,INPUT_PULLUP);
closed=lastRaw=digitalRead(27)==LOW;
latch.setPeriodHertz(50);latch.attach(13,500,2400);latch.write(closed?10:90);
prefs.begin("locker",false);state=prefs.getString("state","empty");category=prefs.getString("category","normal");target=category=="ventilated"?28:30;
// Interrupted transactions need admin inspection; never infer delivery from reboot.
if(state!="empty" && state!="waiting")state="recovery";
sensor.begin();Wire.begin(21,22);Wire.setClock(100000);lcd.init();lcd.backlight();
WiFi.mode(WIFI_AP);WiFi.softAP("SmartLocker-Demo","LockerDemo2026");
const char* headers[]={"X-Admin-Key"};server.collectHeaders(headers,1);
server.on("/",HTTP_GET,[](){server.send_P(200,"text/html; charset=utf-8",LOCKER_UI);});
server.on("/status",HTTP_GET,status);
server.on("/deposit",HTTP_POST,[](){
if(!auth())return;
if(state!="empty"||!closed){reply(409,"{\"error\":\"not_empty_or_door_open\"}");return;}
category=server.arg("category");if(category!="normal"&&category!="ventilated"){reply(400,"{\"error\":\"category\"}");return;}
target=category=="ventilated"?28:30;state="loading";openingSeen=false;unlockedAt=millis();latch.write(90);save();status();
});
server.on("/otp",HTTP_POST,[](){if(!auth())return;if(state!="waiting"){reply(409,"{}");return;}mint();reply(200,"{\"otp\":\""+otp+"\",\"expiresSeconds\":300}");});
server.on("/unlock",HTTP_POST,[](){
if(blocked){reply(429,"{\"error\":\"wait_60_seconds\"}");return;}
if(state!="waiting"||otp.length()!=6||millis()-issued>=300000){otp="";reply(403,"{\"error\":\"expired_or_missing\"}");return;}
if(server.arg("otp")!=otp){if(++failures>=5){blocked=true;blockedAt=millis();otp="";}reply(403,"{\"error\":\"wrong_otp\"}");return;}
otp="";state="collecting";openingSeen=false;unlockedAt=millis();latch.write(90);save();status();
});
server.on("/reset",HTTP_POST,[](){if(!auth())return;if(!closed){reply(409,"{}");return;}state="empty";otp="";latch.write(10);save();status();});
server.begin();Serial.println("WiFi SmartLocker-Demo / LockerDemo2026; http://192.168.4.1");
}
void loop(){
server.handleClient();uint32_t now=millis();
rawClosed=digitalRead(27)==LOW;if(rawClosed!=lastRaw){lastRaw=rawClosed;edge=now;}if(now-edge>80)closed=rawClosed;
if(blocked&&now-blockedAt>=60000){blocked=false;failures=0;}
if(state=="loading"||state=="collecting"){
if(!closed)openingSeen=true;
if(closed && openingSeen){latch.write(10);state=state=="loading"?"waiting":"empty";save();}
else if(closed && now-unlockedAt>30000){latch.write(10);state=state=="loading"?"empty":"waiting";save();}
}
if(now-sample>=2500){
sample=now;temperature=sensor.readTemperature();humidity=sensor.readHumidity();
digitalWrite(25,HIGH);delayMicroseconds(10);digitalWrite(25,LOW);
unsigned long duration=pulseIn(18,HIGH,25000);distanceCm=duration?duration/58.0f:NAN;
}
bool next=fan;
if(isnan(temperature))next=true;
else if(temperature>=target+0.5f)next=true;
else if(temperature<=target-0.5f)next=false;
fan=next;digitalWrite(14,fan?HIGH:LOW);
// LEDs simulate thermal modes only: no Peltier is connected.
digitalWrite(23,!isnan(temperature)&&fan);
digitalWrite(19,!isnan(temperature)&&temperature<target-0.5f);
static uint32_t alarmAt=0;
if((!closed && now-unlockedAt>30000)||isnan(temperature)){
if(now-alarmAt>3000){tone(26,4000,100);alarmAt=now;}
}
String line1=isnan(temperature)?"TEMP ERROR":String(temperature,1)+"C "+(fan?"FAN ON":"FAN OFF");
String line2=(closed?String("Closed "):String("Open "))+(isnan(distanceCm)?"--":String(distanceCm,0))+"cm";
if(line1!=previousLine1){lcd.setCursor(0,0);lcd.print(" ");lcd.setCursor(0,0);lcd.print(line1);previousLine1=line1;}
if(line2!=previousLine2){lcd.setCursor(0,1);lcd.print(" ");lcd.setCursor(0,1);lcd.print(line2);previousLine2=line2;}
}
// Pin mapping confirmed: TRIG25 ECHO18 servo13 relay14 door27 buzzer26 SDA21 SCL22.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.




