Community project

Jola Device Controller

ESP32
Photo of Jola Device Controller
Generated with AI

Ernesto Mena

Published October 20, 2026

The Jola Device Controller is an ESP32-based smart lock system that manages access to a secured container using RFID authentication and servo-controlled locking. It combines an MFRC522 RFID reader, reed switches for key and lid detection, a servo motor for the latch mechanism, and status LEDs to provide real-time feedback on the device state.

This guide provides a complete wiring diagram, parts list, and step-by-step assembly instructions for building the controller. The included firmware enables WiFi connectivity and a web interface accessible from a mobile device, allowing remote monitoring of loan approval status, key presence, and lid position. Builders will learn how to integrate RFID authentication with mechanical actuation and web-based control in a single project.

Wiring diagram

Gather all the parts

QtyComponent
1

MFRC522 RFID Module

13.56 MHz RFID reader/writer module based on the NXP MFRC522 IC. Communicates over SPI and is commonly sold as an RC522 breakout with an onboard antenna.

1

LED

Verde

Standard 3mm/5mm through-hole LED. A current-limiting series resistor is added automatically.

1

LED

Rojo

Standard 3mm/5mm through-hole LED. A current-limiting series resistor is added automatically.

1

Micro Switch - Premium Zippy 3-Terminal

Bajo el soporte de la llave

Higher-quality 3-terminal bump-actuator microswitch for arcade controls and light mechatronics, rated 30 VDC / 250 mA max. Common, normally-open, and normally-closed contacts.

1

SG90 Servo

Acciona el pestillo

Micro servo motor (SG90)

1

Reed Switch

Tapa abierta/cerrada

Glass-encapsulated magnetic reed switch -- contacts close in the presence of a magnetic field. Standard door/window sensor for alarms. Wire as a digital input with internal pull-up; debounce in firmware.

1

Módulo de alimentación USB de 5 V

El pequeño módulo que entrega 5 V al ESP32 y al microservo dentro del compartimento separado.

1

Celular con aplicación web

Teléfono que muestra Solicitar llave, Aprobar préstamo e Historial y se comunica por Wi-Fi.

Assemble it in 5 steps

1. Prepara el frente de la caja

En el frente de la caja de 20 × 15 × 10 cm coloca el lector RFID y los dos LED: el verde para autorización y el rojo para rechazo. Deja visible la antena plana del RFID y marca cada LED con una etiqueta corta.

  • Coloca los LED junto al lector para que se entiendan como una sola zona de acceso.
  • No conectes el lector RFID RC522 a 5 V: su alimentación es de 3,3 V y 5 V puede dañarlo.

2. Monta la llave y sus detectores

Dentro de la caja fija el soporte de la llave y sitúa el microinterruptor debajo para que la llave lo presione cuando está guardada. Coloca el sensor magnético en el borde de la tapa y su imán enfrente, de modo que detecte cuando la tapa se separa.

  • Comprueba a mano que la llave pulsa el sensor sin quedarse atascada y que el imán queda cerca del sensor al cerrar.
  • Un sensor mal alineado puede indicar que la llave está presente o que la tapa está cerrada cuando no lo está.

3. Instala el pestillo

Atornilla el microservo cerca del cierre y une su brazo al pestillo para que un giro corto libere la tapa. Prueba el recorrido sin alimentar el servo para evitar que el mecanismo fuerce la tapa.

  • Deja un pequeño margen mecánico al final del recorrido; el servo no debe seguir empujando contra un tope.
  • Si el pestillo se traba, el servo puede calentarse y dañarse.

4. Separa control y alimentación

En el compartimento separado instala el ESP32 y el módulo de alimentación de 5 V. Mantén el módulo de 5 V cerca del servo y lleva una tierra común a todos los módulos.

  • Usa una fuente USB de 5 V capaz de entregar suficiente corriente para el movimiento breve del servo.
  • No intercambies 5 V, 3,3 V y GND: una conexión de alimentación equivocada puede dañar el ESP32 o el lector RFID.

5. Conecta los módulos según el lienzo

Sigue las líneas del esquema de Schematik: el lector RFID, los LED, el sensor bajo la llave, el sensor de tapa y el servo llegan al ESP32; la aplicación del celular se comunica por Wi‑Fi, sin cable físico.

  • Los dos LED necesitan su resistencia limitadora; el esquema la añade con cada LED.
  • Antes de cerrar la caja, verifica que ningún cable pueda rozar el brazo del servo ni el pestillo.

Review all connections

1. Connections between "rfid_frontal" and "ESP32"

Functionrfid_frontalESP32
powerVCC3V3
groundGNDGND
spiSCKGPIO 18
spiMOSIGPIO 23
spiMISOGPIO 19
spiSDAGPIO 4
digitalRSTGPIO 27

2. Connections between "led_verde_frontal" and "ESP32"

Functionled_verde_frontalESP32
groundGNDGND
digitalANODEGPIO 32

3. Connections between "led_rojo_frontal" and "ESP32"

Functionled_rojo_frontalESP32
groundGNDGND
digitalANODEGPIO 33

4. Connections between "sensor_llave" and "ESP32"

Functionsensor_llaveESP32
groundCOMGND
digitalNOGPIO 14

5. Connections between "servo_pestillo" and "ESP32"

Functionservo_pestilloESP32
powerVCC5V
groundGNDGND
pwmSIGNALGPIO 25

6. Connections between "sensor_tapa" and "ESP32"

Functionsensor_tapaESP32
dataPIN1GPIO 13
groundPIN2GND

7. Connections between "alimentacion_5v" and "ESP32"

Functionalimentacion_5vESP32
power5V OUT5V
groundGNDGND
powerUSB 5V → Fuente USB externa de 5 VEXT

Deploy the firmware

#include <WiFi.h>
#include <WebServer.h>
#include <SPI.h>
#include <MFRC522.h>
#include <ESP32Servo.h>

constexpr uint8_t RFID_SS_PIN = 4;
constexpr uint8_t RFID_RST_PIN = 27;
constexpr uint8_t LED_GREEN_PIN = 32;
constexpr uint8_t LED_RED_PIN = 33;
constexpr uint8_t KEY_SENSOR_PIN = 14;
constexpr uint8_t LID_SENSOR_PIN = 13;
constexpr uint8_t SERVO_PIN = 25;
constexpr int SERVO_LOCKED = 10;
constexpr int SERVO_UNLOCKED = 100;

const char *AP_SSID = "LlaveLink";
const char *AP_PASSWORD = "LlaveLink2026";

MFRC522 rfid(RFID_SS_PIN, RFID_RST_PIN);
Servo lockServo;
WebServer server(80);

bool loanApproved = false;
bool unlocked = false;
bool lastKeyPresent = false;
bool lastLidOpen = false;

void setLock(bool open) {
  unlocked = open;
  lockServo.write(open ? SERVO_UNLOCKED : SERVO_LOCKED);
  digitalWrite(LED_GREEN_PIN, open ? HIGH : LOW);
  digitalWrite(LED_RED_PIN, open ? LOW : HIGH);
}

String statusText() {
  bool keyPresent = digitalRead(KEY_SENSOR_PIN) == HIGH;
  bool lidOpen = digitalRead(LID_SENSOR_PIN) == HIGH;
  String text = "Préstamo: ";
  text += loanApproved ? "aprobado" : "pendiente";
  text += " | Llave: ";
  text += keyPresent ? "en soporte" : "retirada";
  text += " | Tapa: ";
  text += lidOpen ? "abierta" : "cerrada";
  return text;
}

void sendPage() {
  String page = "<!doctype html><html><meta name='viewport' content='width=device-width,initial-scale=1'>";
  page += "<style>body{font-family:Arial;margin:28px;color:#17324d}button{display:block;width:100%;padding:16px;margin:12px 0;font-size:18px;border:0;border-radius:8px;background:#1677c8;color:white}</style>";
  page += "<h1>LlaveLink</h1><p>" + statusText() + "</p>";
  page += "<form action='/request'><button>Solicitar llave</button></form>";
  page += "<form action='/approve'><button>Aprobar préstamo</button></form>";
  page += "<form action='/history'><button>Historial</button></form></html>";
  server.send(200, "text/html; charset=utf-8", page);
}

void setup() {
  Serial.begin(115200);
  pinMode(LED_GREEN_PIN, OUTPUT);
  pinMode(LED_RED_PIN, OUTPUT);
  pinMode(KEY_SENSOR_PIN, INPUT_PULLUP);
  pinMode(LID_SENSOR_PIN, INPUT_PULLUP);

  lockServo.setPeriodHertz(50);
  lockServo.attach(SERVO_PIN, 500, 2400);
  setLock(false);

  SPI.begin();
  rfid.PCD_Init();

  WiFi.softAP(AP_SSID, AP_PASSWORD);
  server.on("/", sendPage);
  server.on("/request", []() { loanApproved = false; server.sendHeader("Location", "/"); server.send(303); });
  server.on("/approve", []() { loanApproved = true; server.sendHeader("Location", "/"); server.send(303); });
  server.on("/history", []() { server.send(200, "text/plain; charset=utf-8", "Historial conceptual: " + statusText()); });
  server.begin();

  lastKeyPresent = digitalRead(KEY_SENSOR_PIN) == HIGH;
  lastLidOpen = digitalRead(LID_SENSOR_PIN) == HIGH;
  Serial.println("LlaveLink listo. Conéctate a la red Wi-Fi LlaveLink.");
}

void loop() {
  server.handleClient();

  bool keyPresent = digitalRead(KEY_SENSOR_PIN) == HIGH;
  bool lidOpen = digitalRead(LID_SENSOR_PIN) == HIGH;
  if (keyPresent != lastKeyPresent || lidOpen != lastLidOpen) {
    lastKeyPresent = keyPresent;
    lastLidOpen = lidOpen;
    Serial.println(statusText());
  }

  if (rfid.PICC_IsNewCardPresent() && rfid.PICC_ReadCardSerial()) {
    if (loanApproved) {
      setLock(true);
      Serial.println("Tarjeta RFID aceptada: pestillo liberado.");
    } else {
      digitalWrite(LED_RED_PIN, HIGH);
      delay(200);
      digitalWrite(LED_RED_PIN, LOW);
      Serial.println("Tarjeta RFID leída: préstamo pendiente de aprobación.");
    }
    rfid.PICC_HaltA();
    rfid.PCD_StopCrypto1();
  }

  delay(10);
}

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