An IoT-based smart locker system for university campuses, providing a secure, space-efficient drop-off and pick-up point for online delivery orders (Gojek, Grab, and similar services).
The system uses dynamic locker allocation, auto-reset PINs, IoT sensors, and real-time status updates so lockers are only occupied when needed while keeping delivered items secure.
Lockers are not permanently assigned to students. An available locker is temporarily allocated when an order is dropped off; once the student picks it up, the locker instantly returns to the pool for the next user.
Every PIN is single-use. The moment the door is closed after a PIN is used, that PIN is invalidated server-side — it can never be reused, including by the driver who just typed it in.
Students don't need to meet drivers in person. Drivers securely place the order inside the assigned locker; students retrieve it later with their own PIN. This also reduces congestion at campus gates/security posts.
React.js App (Student)
│ REST API
▼
Node.js + Express Backend
│
┌───────┴───────┐
▼ ▼
MongoDB / MQTT Broker
Firebase │ MQTT
(locker & ▼
booking data) ESP32
│
┌────────────┼────────────┐
▼ ▼ ▼
Keypad Door Sensor Relay
│
▼
Solenoid Lock
| Component | Description |
|---|---|
| ESP32 | Main microcontroller, built-in Wi-Fi |
| 12V Solenoid Door Lock | Electronic lock securing the locker door |
| Relay Module | Switches the 12V solenoid from the ESP32's logic-level output |
| 4x4 / 4x3 Keypad | PIN entry for both drivers and students |
| Limit Switch / Magnetic Door Sensor | Detects open/closed door state — triggers PIN auto-reset |
| 12V Power Supply | Powers the solenoid lock |
| Power Converter | Steps down voltage for ESP32 and other low-voltage components |
Hardware flow: PIN entered on keypad → ESP32 publishes to MQTT → backend validates → command sent back → relay energizes → solenoid unlocks → door opens → door sensor detects closure → ESP32 reports closed status → backend invalidates the PIN just used.
| Layer | Technology |
|---|---|
| Frontend | React.js (Vite) |
| Backend | Node.js + Express.js |
| Database | MongoDB or Firebase Realtime Database (lowdb/JSON for local prototyping) |
| IoT Communication | MQTT |
| Microcontroller | ESP32 (Arduino framework) |
Frontend responsibilities: locker grid with real-time status, borrow form, PIN display, physical-keypad simulator for demos, responsive layout for all screen sizes (mobile/tablet/desktop media queries).
Backend responsibilities: locker allocation, PIN generation & validation, booking state management, MQTT communication bridge, activity logging.
Each borrow cycle uses three PINs:
| PIN | Set by | Purpose | Lifetime |
|---|---|---|---|
| Driver PIN (primary) | Auto-generated by system | Opens the locker for the main drop-off | Invalidated the moment the door closes after use |
| Driver PIN (backup) | Auto-generated by system | Optional — only needed if the driver forgot part of the order and must open the locker a second time | Invalidated on use; never required to be used at all |
| Student PIN | Chosen by the student themselves in the borrow form | Opens the locker to retrieve the order | Inactive until the first drop-off happens; invalidated the moment the door closes after pickup |
Rules:
- Every PIN is single-use — no PIN can open the locker twice.
- The student's own PIN only becomes usable after at least one driver PIN has been used for drop-off (so an empty locker can't be "picked up").
- Once the student's PIN is used and the door is closed, the locker auto-locks and instantly returns to
EMPTY, available for the next student — no manual "Done" confirmation required. - Failed PIN attempts are logged; repeated failures should trigger a temporary keypad lockout in production.
- MQTT traffic should be authenticated/encrypted (TLS) in production.
EMPTY
│ student picks locker, fills form (name + NIM)
│ + creates their own pickup PIN
│ → system auto-generates 2 driver PINs
▼
BOOKED ← other users see this as "Currently Borrowed"
│ driver enters primary (or backup) PIN, drops item, closes door
▼
FILLED (student PIN becomes active)
│ driver may optionally re-enter with backup PIN for forgotten items
│ (locker stays FILLED, backup PIN just gets consumed)
│
│ student enters their own PIN, takes item, closes door
▼
EMPTY (auto-reset, all PINs cleared, ready for next student)
Optional admin-facing states from the original design (for production hardening): OFFLINE, MAINTENANCE, and a BOOKED → EMPTY timeout (e.g. 45 minutes with no drop-off) to prevent ghost bookings.
- Student selects an EMPTY locker from the grid.
- Student is taken to a borrow form: enters name + NIM, and creates their own 4-digit pickup PIN.
- On submit, the locker instantly becomes BOOKED — this is immediately visible to all other students as "Currently Borrowed".
- System auto-generates the primary and backup driver PINs and shows them to the student to forward to the driver via chat.
- Driver enters the primary PIN on the physical keypad.
- ESP32 validates via the backend and unlocks the solenoid.
- Driver places the item and closes the door.
- Door sensor detects closure → backend invalidates the driver PIN just used and activates the student's pickup PIN.
- Locker status → FILLED.
- If the driver forgot part of the order, they use the backup PIN instead of asking the student to generate anything new.
- Same open → place item → close → PIN-invalidated cycle applies.
- The backup PIN may simply never be used — this has no effect on the rest of the flow.
- Student enters their own PIN on the keypad.
- Locker unlocks; student retrieves the item(s).
- Student closes the door.
- Door sensor detects closure → backend invalidates the student PIN and auto-resets the entire locker to EMPTY — no "Done" button needed.
- Locker is immediately available for the next student.
locker/{lockerId}/pin_masuk ESP32 → Backend PIN typed on keypad
locker/{lockerId}/pintu_status ESP32 → Backend "TERTUTUP" (door closed)
locker/{lockerId}/perintah Backend → ESP32 "BUKA" / "TOLAK"
// Locker
{
"lockerId": "L-03",
"status": "FILLED",
"studentName": "Rian",
"studentNim": "5023099",
"studentPinActive": true,
"driverPinPrimaryActive": false,
"driverPinBackupActive": true,
"doorOpenFor": null,
"updatedAt": "2026-09-26T08:00:00Z"
}campus-smart-locker/
├── frontend/ React (Vite) — locker grid, borrow form, detail/keypad-sim pages
│ └── src/{pages,components,api.js}
├── backend/ Express API — locker routes, PIN logic, MQTT bridge
│ └── src/{routes,db.js,server.js,mqtt-bridge.js}
├── hardware/ ESP32 firmware (keypad, relay, door sensor, MQTT)
│ └── esp32_locker.ino
└── README.md
- HTTPS for the REST API; MQTT over TLS with authenticated clients.
- Server-side PIN validation only — never trust the ESP32 to self-authorize.
- Rate-limit and temporarily lock out a keypad after repeated failed PIN attempts.
- Log all PIN generation, usage, and failed attempts.
- Booking timeout (e.g. 45 min with no drop-off) to auto-release ghost bookings.
- Role-based access + audit log for any admin/emergency override PIN.
- Admin dashboard (master locker monitoring, emergency override, overdue tracking)
- Push notifications for drop-off/pickup events
- QR-code-based access as an alternative to keypad PINs
- Automatic package/weight detection
- Backup battery for the lock system
- Multi-campus locker management
- Integration with campus SSO/authentication