Professional grade Smart Lock with ESP32, BLE and Android App Control
By integrating advanced sensors and optimizing the power management of the ESP32, I've created a DIY smart lock that is both reliable and energy-efficient.
Devices & Components
1
Grove - Hall Sensor
1
CURRENT SENSOR ACS712
1
Power MOSFET N-Channel
1
LM7805 Voltage regulator
1
Battery 7.4V lithium
1
Tower Pro SG90 Servo Motor
1
DOIT ESP32 DevKit v1
Hardware & Tools
1
Soldering Iron Kit
Software & Tools
Arduino IDE
Project description
Code
Arduino Code
cpp
...
1// by mircemk March, 2026 2 3#include <BLEDevice.h> 4#include <BLEServer.h> 5#include <BLEUtils.h> 6#include <BLE2902.h> 7#include <ESP32Servo.h> 8#include "esp_pm.h" 9#include "esp_wifi.h" 10 11// --- КОНФИГУРАЦИЈА ПИНОВИ --- 12const int PIN_SERVO = 18; 13const int PIN_HALL = 19; 14const int PIN_CURRENT = 4; 15const int PIN_BATTERY = 35; 16const int PIN_MOSFET = 2; // Контрола на напојување за ACS712 и Servo 17 18// --- ТАЈМЕРИ --- 19unsigned long lastWatchdogTick = 0; 20const unsigned long WATCHDOG_TIMEOUT = 300; 21unsigned long lastBatteryReport = 0; 22const unsigned long BATTERY_REPORT_INTERVAL = 5000; 23 24// --- ПАРАМЕТРИ --- 25RTC_DATA_ATTR int turnsNeeded = 1; 26int OFFSET = 5; 27const int S_U = 4; 28const int S_L = -4; 29const unsigned long IGNORE_TIME = 600; 30const unsigned long SOFT_START_TIME = 400; 31const unsigned long BRAKE_PULSE = 25000; 32const int CURRENT_LIMIT = 920; 33const int STALL_SAMPLES = 3; 34float vZero = 2.5; 35 36#define SERVICE_UUID "12345678-1234-1234-1234-1234567890ab" 37#define COMMAND_CHAR_UUID "abcd1234-5678-1234-5678-1234567890ab" 38#define STATUS_CHAR_UUID "dcba4321-8765-4321-8765-1234567890ab" 39 40Servo myServo; 41BLECharacteristic *pStatusCharacteristic; 42bool deviceConnected = false; 43bool lastConnectionState = false; 44volatile bool magnetHit = false; 45bool hallEnabled = false; 46bool isManualMode = false; 47unsigned long moveStartTime = 0; 48int lastDir = 0; 49int stallCounter = 0; 50int magnetCount = 0; 51String lockStatus = "LOCKED"; 52String lastKnownValidStatus = "LOCKED"; 53 54// --- ПОМОШНИ ФУНКЦИИ --- 55 56void IRAM_ATTR onHall() { 57 if (hallEnabled && !isManualMode) magnetHit = true; 58} 59 60void updateStatus(String newStatus) { 61 lockStatus = newStatus; 62 if (newStatus == "LOCKED" || newStatus == "UNLOCKED") lastKnownValidStatus = newStatus; 63 pStatusCharacteristic->setValue(lockStatus.c_str()); 64 pStatusCharacteristic->notify(); 65} 66 67void stopAction(String finalStatus) { 68 int tempDir = lastDir; 69 lastDir = 0; moveStartTime = 0; hallEnabled = false; stallCounter = 0; magnetCount = 0; 70 71 if (tempDir == 1) myServo.write(S_L + 90 + OFFSET); 72 else if (tempDir == -1) myServo.write(S_U + 90 + OFFSET); 73 74 if (tempDir != 0) ets_delay_us(BRAKE_PULSE); 75 myServo.write(90 + OFFSET); 76 77 // ИСКЛУЧИ ПЕРИФЕРИЈА (Штедење енергија) 78 digitalWrite(PIN_MOSFET, LOW); 79 80 updateStatus(finalStatus); 81} 82 83float readBatteryVoltage() { 84 long sum = 0; 85 const int samples = 40; 86 for (int i = 0; i < samples; i++) { 87 sum += analogRead(PIN_BATTERY); 88 delayMicroseconds(400); 89 } 90 float adcRaw = (float)sum / (float)samples; 91 float vAdc = (adcRaw / 4095.0f) * 3.3f; 92 float vBat = vAdc * 3.2f * 1.1f; 93 return vBat; 94} 95 96void reportBatteryVoltage() { 97 float vb = readBatteryVoltage(); 98 if (deviceConnected) { 99 String msg = "BAT:" + String(vb, 2) + "V"; 100 pStatusCharacteristic->setValue(msg.c_str()); 101 pStatusCharacteristic->notify(); 102 } 103} 104 105float readCurrent() { 106 long sum = 0; 107 for (int i = 0; i < 15; i++) sum += analogRead(PIN_CURRENT); 108 float voltage = ((float)sum / 15.0f * 3.3f) / 4095.0f; 109 float current = (voltage - vZero) / 0.185f; 110 return abs(current * 1000.0f); 111} 112 113void handleCommand(char cmd) { 114 // Пред било која акција, ВКЛУЧИ напојување за мотор и сензор 115 if (cmd == 'U' || cmd == 'L' || cmd == '[' || cmd == ']' || cmd == 'M') { 116 digitalWrite(PIN_MOSFET, HIGH); 117 delay(20); // Пауза за стабилизација на напонот 118 } 119 120 if (isManualMode) { 121 if (cmd == '[' || cmd == ']') { 122 lastWatchdogTick = millis(); 123 if (cmd == '[') { lastDir = 1; myServo.write(S_U + 90 + OFFSET); } 124 else { lastDir = -1; myServo.write(S_L + 90 + OFFSET); } 125 return; 126 } 127 if (cmd == '1') { turnsNeeded = 1; updateStatus("SET_1_TURN"); } 128 else if (cmd == '2') { turnsNeeded = 2; updateStatus("SET_2_TURNS"); } 129 else if (cmd == 'S') { stopAction("MAN_STOP"); } 130 if (cmd == '1' || cmd == '2' || cmd == 'S') { delay(500); updateStatus(lastKnownValidStatus); } 131 } 132 133 if (cmd == 'M') { 134 isManualMode = !isManualMode; 135 stopAction(isManualMode ? "MANUAL_ON" : "MANUAL_OFF"); 136 if (!isManualMode) { delay(500); updateStatus(lastKnownValidStatus); } 137 } 138 else if (!isManualMode) { 139 if (cmd == 'U' || cmd == 'L') { 140 magnetHit = false; hallEnabled = false; stallCounter = 0; magnetCount = 0; 141 moveStartTime = millis(); 142 if (cmd == 'U') { lastDir = 1; myServo.write(S_U + 90 + OFFSET); updateStatus("UNLOCKING"); } 143 else { lastDir = -1; myServo.write(S_L + 90 + OFFSET); updateStatus("LOCKING"); } 144 } 145 else if (cmd == 'S') { stopAction(lockStatus); } 146 } 147} 148 149class MyServerCallbacks: public BLEServerCallbacks { 150 void onConnect(BLEServer* pServer) { deviceConnected = true; } 151 void onDisconnect(BLEServer* pServer) { deviceConnected = false; BLEDevice::startAdvertising(); } 152}; 153 154class CommandCallbacks: public BLECharacteristicCallbacks { 155 void onWrite(BLECharacteristic *pCharacteristic) { 156 std::string rxValue = pCharacteristic->getValue(); 157 if (rxValue.length() > 0) handleCommand(rxValue[0]); 158 } 159}; 160 161void setup() { 162 // 1. ЕНЕРГЕТСКА ОПТИМИЗАЦИЈА (Автоматски Light Sleep помеѓу BLE настани) 163 esp_wifi_stop(); 164 esp_pm_config_esp32_t pm_config; 165 pm_config.max_freq_mhz = 80; 166 pm_config.min_freq_mhz = 10; 167 pm_config.light_sleep_enable = true; 168 esp_pm_configure(&pm_config); 169 170 Serial.begin(115200); 171 172 // 2. MOSFET SETUP (Главен прекинувач) 173 pinMode(PIN_MOSFET, OUTPUT); 174 digitalWrite(PIN_MOSFET, LOW); // Почни со исклучена периферија 175 176 // 3. ХАРДВЕР 177 ESP32PWM::allocateTimer(0); 178 myServo.setPeriodHertz(50); 179 myServo.attach(PIN_SERVO, 500, 2400); 180 pinMode(PIN_HALL, INPUT_PULLUP); 181 attachInterrupt(digitalPinToInterrupt(PIN_HALL), onHall, FALLING); 182 pinMode(PIN_BATTERY, INPUT); 183 analogReadResolution(12); 184 185 // 4. BLE SETUP 186 esp_bt_controller_mem_release(ESP_BT_MODE_CLASSIC_BT); 187 BLEDevice::init("BLE_LOCK_TEST"); 188 BLEServer *pServer = BLEDevice::createServer(); 189 pServer->setCallbacks(new MyServerCallbacks()); 190 BLEService *pService = pServer->createService(SERVICE_UUID); 191 192 BLECharacteristic *pCmdChar = pService->createCharacteristic(COMMAND_CHAR_UUID, BLECharacteristic::PROPERTY_WRITE | BLECharacteristic::PROPERTY_READ); 193 pCmdChar->setCallbacks(new CommandCallbacks()); 194 195 pStatusCharacteristic = pService->createCharacteristic(STATUS_CHAR_UUID, BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_NOTIFY); 196 pStatusCharacteristic->addDescriptor(new BLE2902()); 197 pStatusCharacteristic->setValue(lockStatus.c_str()); 198 199 pService->start(); 200 BLEDevice::getAdvertising()->start(); 201 202 myServo.write(90 + OFFSET); 203 Serial.println("System v1.7 Ready - Peripherals OFF"); 204} 205 206void loop() { 207 if (Serial.available() > 0) handleCommand(Serial.read()); 208 209 if (deviceConnected && !lastConnectionState) { 210 updateStatus(lockStatus); 211 } 212 lastConnectionState = deviceConnected; 213 214 if (millis() - lastBatteryReport >= BATTERY_REPORT_INTERVAL) { 215 lastBatteryReport = millis(); 216 reportBatteryVoltage(); 217 } 218 219 if (isManualMode && lastDir != 0) { 220 if (millis() - lastWatchdogTick > WATCHDOG_TIMEOUT) stopAction(lastKnownValidStatus); 221 } 222 223 if (lastDir != 0 && !isManualMode) { 224 if (millis() - moveStartTime > SOFT_START_TIME) { 225 float current = readCurrent(); 226 if (current > CURRENT_LIMIT) { 227 stallCounter++; 228 if (stallCounter >= STALL_SAMPLES) stopAction("Z"); 229 } else { if (stallCounter > 0) stallCounter--; } 230 } 231 if (magnetHit) { 232 magnetCount++; 233 if (magnetCount >= turnsNeeded) stopAction(lastDir == 1 ? "UNLOCKED" : "LOCKED"); 234 else { magnetHit = false; hallEnabled = false; moveStartTime = millis(); } 235 } 236 if (!hallEnabled && moveStartTime != 0 && (millis() - moveStartTime > IGNORE_TIME)) hallEnabled = true; 237 } 238 239 delay(10); 240}
Downloadable files
Codelock APK
Android app.
Codelock APK.zip
Documentation
Schematic
...
Schematic.jpg

Comments
Only logged in users can leave comments