---

Tuesday, March 31, 2026

ESP32 and Bluetooth Experiments to Make a Door Lock

This project uses and ESP32 Lite board (programmed with the Arduino) to BLE scan for nearby devices and allow a door to be unlocked.


Warning: This code is experimental and the whole principal is not very secure. But it is a bit of fun. Use at your own risk.

This Arduino sketch is a work in progress - it does work, with some limitations.

It uses the MAC addresses from nearby devices (such as a smart watch) to unlock an electromechanical door latch. This is a two stage process. First when one of the registered devices is recognised on a BLE scan, an LED turns on to indicate that if you press the button the door will unlock.

The idea is to use the signal strength of the key device to only enable the unlock button when you get quite close. 

Wiring 

   - LED to GPIO 27

   - Push switch to GPIO 32

   - Latch control GPIO 33 - Use n-ch MOSFET or TIP120 to low-side switch the 12V relay.

I used a nice water-proof switch that has an LED ring around the outside to combine the LED and push switch.

Code

This code uses the ESP32 library from Espressif: https://github.com/espressif/arduino-esp32

Its also on Github here, where there might be a more up-to-date version.

/*
   ESP32 Wroom
   When device recognised, LED on and wait fo Outside Button Press (with timeout).
   When button pressed unlock door for short period, during which LED flashes.

   Wiring 
   - LED to GPIO 27
   - Push switch to GPIO 32
   - Latch control GPIO 33 - Use n-ch MOSFET or TIP120 to low-side switch the 12V relay.

    
todo
- timeout on waiting for unlock - 1 min
- Inside Button immediately unlocks for shorter period, so can shut door again quickly.
*/

#include <BLEDevice.h>
#include <BLEUtils.h>
#include <BLEScan.h>
#include <BLEAdvertisedDevice.h>

int scanTime = 5;  //In seconds
const long ACTIVATION_PERIOD = 5000; // ms door is unlocked
const long BLINK_PERIOD = 100; // ms blink delay

BLEScan *pBLEScan;

const int LED_PIN = 27;
const int SWITCH_PIN = 32;
const int LATCH_PIN = 33;

const int RSSI_THRESHOLD = -90; // bypass just scan all

enum states {SCANNING, KEY_DEVICE_FOUND, UNLOCKING};
enum states state = SCANNING;

const int NUM_KNOWN_DEVICES = 1;
const char KNOWN_DEVICES[NUM_KNOWN_DEVICES][18] = {
  "ea:3c:61:46:ba:2c"
};

long unlockingStartTime = 0;
long lastBlinkTime = 0;
int ledState = 0;

boolean isKnown(String address) {
  for (int i = 0; i < NUM_KNOWN_DEVICES; i++) {
    if (address == String(KNOWN_DEVICES[i])) {
      return true;
    }
  }
  return false;
}
        

class MyAdvertisedDeviceCallbacks : public BLEAdvertisedDeviceCallbacks {
  void onResult(BLEAdvertisedDevice advertisedDevice) {
    int sig = advertisedDevice.getRSSI();
    //if (sig > RSSI_THRESHOLD) {
      Serial.printf("Advertised Device: %s \n", advertisedDevice.toString().c_str());
      String address = advertisedDevice.getAddress().toString();
      if (isKnown(address)) {
        pBLEScan->stop();
        //pBLEScan->clearResults();
        Serial.println("KEY_DEVICE_FOUND");
        state = KEY_DEVICE_FOUND;
      }
    //}
  }
};

void setup() {
  Serial.begin(9600);
  Serial.println("Scanning...");
  pinMode(LED_PIN, OUTPUT);
  pinMode(LATCH_PIN, OUTPUT);
  pinMode(SWITCH_PIN, INPUT_PULLUP);

  BLEDevice::init("");
  pBLEScan = BLEDevice::getScan();  //create new scan
  pBLEScan->setAdvertisedDeviceCallbacks(new MyAdvertisedDeviceCallbacks());
  pBLEScan->setActiveScan(true);  //active scan uses more power, but get results faster
  pBLEScan->setInterval(100);
  pBLEScan->setWindow(99);  // less or equal setInterval value
}

void loop() {
  if (state == SCANNING) {
    Serial.println("SCANNING");
    BLEScanResults *foundDevices = pBLEScan->start(scanTime, false);
    Serial.println("---");
    pBLEScan->clearResults();  // delete results fromBLEScan buffer to release memory
  }
  else if (state == KEY_DEVICE_FOUND) {
    digitalWrite(LED_PIN, HIGH);
    if (digitalRead(SWITCH_PIN) == 0) {
      Serial.println("UNLOCKING");
      state = UNLOCKING;
      unlockingStartTime = millis();
    }
  }
  else if (state == UNLOCKING) {
    digitalWrite(LATCH_PIN, HIGH);
    // blink and check for end time
    long now = millis();
    if (now > unlockingStartTime + ACTIVATION_PERIOD) {
      digitalWrite(LATCH_PIN, LOW);
      digitalWrite(LED_PIN, LOW);
      state = SCANNING;
    }
    else {
      if (now > lastBlinkTime + BLINK_PERIOD) {
        ledState = ! ledState;
        digitalWrite(LED_PIN, ledState);
        lastBlinkTime = now;
      }
    }
  }
}


Notes

There is a lot of useful trace, so you can find addresses for key devices, so open the Arduino IDE serial monitor at 9600 baud.

Works well for my smart watch (Withings), but my phone uses random MAC addreses, rendering it useless for this application.

The LED in the switch unit is too dim at 3.3V (not even drawing 1mA) to see in daylight. I think a buzzer would help.

No comments: