쪽지발송 성공
Click here
재능넷 이용방법
재능넷 이용방법 동영상편
가입인사 이벤트
판매 수수료 안내
안전거래 TIP
재능인 인증서 발급안내

🌲 지식인의 숲 🌲

🌳 디자인
🌳 음악/영상
🌳 문서작성
🌳 번역/외국어
🌳 프로그램개발
🌳 마케팅/비즈니스
🌳 생활서비스
🌳 철학
🌳 과학
🌳 수학
🌳 역사
해당 지식과 관련있는 인기재능

 <해석 필수입니다. 영어로 긴 내용이 담긴 문서들은  해석이나 요약이라도 하세요. 이 사이트에 번역재능들은 뻘로 있는 것이 아닙...

 ​학생 과제, 기업외주, 연구과제, 제품 프로토타입, 기술자문 등 모두 가능합니다.학부생 머신러닝, 데이터 시각화 등 과제 가능합니다.학석...

안녕하세요, 개발자 Seagull입니다^^재능넷을 통해 접수 받는 분야는 다음과 같습니다. - C, C++, C#, Python → 알고리즘 구현 (기본 알고리...

하드웨어 개발 부서에서 7년 넘게 근무하였습니다. verilog 코딩 경력은 10년, vhdl 코딩 경력은 1년, systemverilog 코딩은 1년 됩니다.&nbs...

ESP32: Bluetooth LE 기반 스마트 잠금장치 개발

2024-09-13 16:24:36

재능넷
조회수 31 댓글수 0

ESP32: Bluetooth LE 기반 스마트 잠금장치 개발 🔒

 

 

스마트 홈 시대가 도래하면서, 우리의 일상 생활 속 다양한 기기들이 점점 더 지능화되고 있습니다. 그 중에서도 스마트 잠금장치는 보안과 편의성을 동시에 높여주는 핵심 기술로 주목받고 있죠. 이번 글에서는 ESP32 마이크로컨트롤러와 Bluetooth Low Energy(BLE) 기술을 활용하여 스마트 잠금장치를 개발하는 방법에 대해 상세히 알아보겠습니다.

이 프로젝트는 IoT 개발자, 임베디드 시스템 엔지니어, 그리고 홈 오토메이션에 관심 있는 모든 분들에게 유용한 정보가 될 것입니다. 재능넷의 '지식인의 숲' 메뉴를 통해 이러한 실용적인 기술 지식을 공유하고, 더 나아가 관련 분야의 전문가들과 연결될 수 있는 기회를 제공하고자 합니다.

그럼 지금부터 ESP32와 BLE를 이용한 스마트 잠금장치 개발의 세계로 함께 빠져보시죠! 🚀

1. ESP32 소개 및 특징 🖥️

ESP32는 Espressif Systems에서 개발한 저비용, 저전력 시스템온칩(SoC) 마이크로컨트롤러입니다. WiFi와 Bluetooth 기능이 내장되어 있어 IoT 프로젝트에 매우 적합한 선택지로 각광받고 있습니다.

1.1 ESP32의 주요 특징

  • 듀얼 코어 프로세서: 240MHz의 클럭 속도를 가진 Xtensa LX6 듀얼 코어 프로세서
  • 무선 연결성: WiFi (2.4 GHz band) 및 Bluetooth 4.2 지원
  • 메모리: 520 KB SRAM
  • 저전력 소비: 딥 슬립 모드에서 10µA 미만의 전류 소비
  • 풍부한 주변장치: 캐패시티브 터치 센서, ADC, DAC, I2C, UART, SPI 등 지원

1.2 ESP32와 Arduino의 비교

ESP32는 Arduino와 비교했을 때 몇 가지 뚜렷한 장점을 가지고 있습니다:

ESP32 vs Arduino ESP32 • 듀얼 코어 프로세서 (240 MHz) • 내장 WiFi 및 Bluetooth • 더 큰 메모리 (520 KB SRAM) • 다양한 저전력 모드 Arduino (UNO 기준) • 싱글 코어 프로세서 (16 MHz) • 무선 기능 없음 (별도 모듈 필요) • 제한된 메모리 (2 KB SRAM) • 제한적인 전력 관리

이러한 특징들로 인해 ESP32는 스마트 잠금장치와 같은 복잡한 IoT 프로젝트에 더욱 적합한 선택이 될 수 있습니다. 특히 내장된 Bluetooth 기능은 우리의 프로젝트에 핵심적인 역할을 하게 될 것입니다.

1.3 ESP32 개발 환경 설정

ESP32를 사용하기 위해서는 적절한 개발 환경을 설정해야 합니다. 여기서는 Arduino IDE를 사용한 방법을 소개하겠습니다.

  1. Arduino IDE 설치: Arduino 공식 웹사이트에서 최신 버전의 Arduino IDE를 다운로드하고 설치합니다.
  2. ESP32 보드 매니저 URL 추가:
    • Arduino IDE를 실행하고 File > Preferences 메뉴로 이동합니다.
    • "Additional Board Manager URLs" 필드에 다음 URL을 추가합니다: https://dl.espressif.com/dl/package_esp32_index.json
  3. ESP32 보드 패키지 설치:
    • Tools > Board > Boards Manager 메뉴로 이동합니다.
    • 검색창에 "esp32"를 입력하고 "ESP32 by Espressif Systems"를 찾아 설치합니다.
  4. ESP32 보드 선택: Tools > Board 메뉴에서 사용 중인 ESP32 보드 모델을 선택합니다.
  5. 필요한 라이브러리 설치: 프로젝트에 필요한 추가 라이브러리들을 Sketch > Include Library > Manage Libraries 메뉴를 통해 설치합니다.

이제 ESP32를 위한 기본적인 개발 환경이 준비되었습니다. 다음 섹션에서는 Bluetooth Low Energy의 기본 개념과 ESP32에서의 BLE 구현에 대해 자세히 알아보겠습니다.

2. Bluetooth Low Energy (BLE) 기술 이해하기 📡

Bluetooth Low Energy(BLE)는 기존의 Bluetooth Classic에 비해 훨씬 적은 전력을 소비하면서도 비슷한 통신 범위를 제공하는 무선 통신 기술입니다. 이러한 특성 때문에 BLE는 IoT 기기, 웨어러블 디바이스, 그리고 우리의 스마트 잠금장치와 같은 배터리 구동 장치에 매우 적합합니다.

2.1 BLE의 주요 특징

  • 저전력 소비: BLE는 매우 낮은 전력을 소비하여 배터리 수명을 크게 연장시킵니다.
  • 빠른 연결: 기기 간 연결 시간이 매우 짧아 즉각적인 반응이 가능합니다.
  • 작은 데이터 패킷: 작은 크기의 데이터를 효율적으로 전송할 수 있습니다.
  • 보안: AES-128 암호화를 지원하여 안전한 통신이 가능합니다.

2.2 BLE 프로토콜 스택

BLE 프로토콜 스택은 크게 세 부분으로 나눌 수 있습니다:

BLE 프로토콜 스택 애플리케이션 계층 GATT (Generic Attribute Profile) 호스트 계층 L2CAP, ATT, SM, GAP 컨트롤러 계층 링크 계층, 물리 계층
  1. 애플리케이션 계층: GATT(Generic Attribute Profile)를 포함하며, 실제 데이터 교환을 담당합니다.
  2. 호스트 계층: L2CAP(Logical Link Control and Adaptation Protocol), ATT(Attribute Protocol), SM(Security Manager), GAP(Generic Access Profile) 등을 포함합니다.
  3. 컨트롤러 계층: 물리적인 무선 통신을 담당하는 링크 계층과 물리 계층으로 구성됩니다.

2.3 GATT (Generic Attribute Profile)

GATT는 BLE 장치 간의 데이터 교환 방식을 정의합니다. GATT는 서비스와 특성(Characteristic)으로 구성됩니다.

  • 서비스: 특정 기능이나 기능의 집합을 나타냅니다. 예를 들어, 배터리 서비스, 심박수 서비스 등이 있습니다.
  • 특성: 서비스 내의 실제 데이터 값을 나타냅니다. 예를 들어, 배터리 잔량, 현재 심박수 등이 특성에 해당합니다.

각 서비스와 특성은 고유한 UUID(Universally Unique Identifier)로 식별됩니다.

2.4 BLE 통신 과정

BLE 통신은 크게 다음과 같은 단계로 이루어집니다:

  1. 광고 (Advertising): BLE 장치가 자신의 존재를 알리는 단계입니다.
  2. 스캔 (Scanning): 다른 BLE 장치가 광고 신호를 탐지하는 단계입니다.
  3. 연결 (Connection): 두 장치가 연결을 설정하는 단계입니다.
  4. 데이터 교환: GATT를 통해 실제 데이터를 주고받는 단계입니다.

2.5 ESP32에서의 BLE 구현

ESP32는 BLE 스택을 내장하고 있어, 별도의 하드웨어 없이 BLE 기능을 구현할 수 있습니다. ESP32의 BLE 라이브러리를 사용하면 다음과 같은 작업을 수행할 수 있습니다:

  • BLE 서버 또는 클라이언트 역할 수행
  • 사용자 정의 서비스 및 특성 생성
  • BLE 광고 및 스캔
  • 보안 연결 설정

다음 섹션에서는 ESP32를 사용하여 BLE 기반의 스마트 잠금장치를 구현하는 방법에 대해 자세히 알아보겠습니다. 이를 통해 BLE 기술이 실제로 어떻게 응용되는지 이해할 수 있을 것입니다.

3. 스마트 잠금장치 하드웨어 설계 🔧

스마트 잠금장치를 구현하기 위해서는 적절한 하드웨어 구성이 필요합니다. 이 섹션에서는 프로젝트에 필요한 주요 하드웨어 컴포넌트들과 그들의 연결 방법에 대해 자세히 알아보겠습니다.

3.1 주요 하드웨어 컴포넌트

  1. ESP32 개발 보드: 프로젝트의 두뇌 역할을 합니다. WiFi와 BLE 기능을 내장하고 있어 무선 통신이 가능합니다.
  2. 서보 모터: 잠금 장치를 물리적으로 제어합니다. 도어락의 잠금/해제 메커니즘을 작동시킵니다.
  3. 전원 공급 장치: ESP32와 서보 모터에 전원을 공급합니다. 리튬 이온 배터리나 AA 배터리 팩을 사용할 수 있습니다.
  4. LED 표시등: 장치의 상태(잠김/열림, 배터리 부족 등)를 시각적으로 표시합니다.
  5. 푸시 버튼: 수동 제어를 위한 물리적 인터페이스를 제공합니다.
  6. 리드 스위치 (선택사항): 문의 열림/닫힘 상태를 감지합니다.

3.2 하드웨어 연결 다이어그램

다음은 주요 컴포넌트들의 연결을 보여주는 간단한 다이어그램입니다:

스마트 잠금장치 하드웨어 연결 다이어그램 ESP32 서보 모터 GPIO 13 LED GPIO 12 푸시 버튼 GPIO 14 전원 공급 장치 VIN, GND 리드 스위치 GPIO 27

3.3 하드웨어 연결 상세 설명

  1. ESP32와 서보 모터 연결:
    • 서보 모터의 신호선을 ESP32의 GPIO 13에 연결합니다.
    • 서보 모터의 전원선(빨간색)은 ESP32의 3.3V 핀에, 접지선(갈색)은 GND 핀에 연결합니다.
  2. LED 연결:
    • LED의 양극(긴 다리)을 ESP32의 GPIO 12에 연결합니다.
    • LED의 음극(짧은 다리)을 적절한 저항(예: 220Ω)을 통해 GND에 연결합니다.
  3. 푸시 버튼 연결:
    • 푸시 버튼의 한 쪽을 ESP32의 GPIO 14에 연결합니다.
    • 다른 쪽은 GND에 연결합니다.
    • GPIO 14와 3.3V 사이에 10kΩ 풀업 저항을 연결합니다.
  4. 전원 공급 장치 연결:
    • 배터리 팩의 양극을 ESP32의 VIN 핀에 연결합니다.
    • 배터리 팩의 음극을 ESP32의 GND 핀에 연결합니다.
  5. 리드 스위치 연결 (선택사항):
    • 리드 스위치의 한 쪽을 ESP32의 GPIO 27에 연결합니다.
    • 다른 쪽은 GND에 연결합니다.
    • GPIO 27과 3.3V 사이에 10kΩ 풀업 저항을 연결합니다.

3.4 하드웨어 설계 시 고려사항

  • 전력 관리: ESP32와 서보 모터의 전력 소비를 고려하여 적절한 용량의 배터리를 선택해야 합니다. 또한, 저전력 모드를 활용하여 배터리 수명을 연장할 수 있습니다.
  • 안정성: 모든 연결이 안정적이고 견고한지 확인해야 합니다. 느슨한 연결은 오작동의 원인이 될 수 있습니다.
  • 방수/방진: 실외에 설치될 경우, 방수/방진 처리가 필요할 수 있습니다. 적절한 케이스를 사용하여 전자 부품을 보호해야 합니다.
  • 크기: 전체 장치의 크기를 고려하여 컴팩트하게 설계해야 합니다. 기존 도어락 시스템에 쉽게 통합될 수 있어야 합니다.
  • 확장성: 추후 기능 추가(예: 지문 인식, 키패드 등)를 고려하여 여유 GPIO를 남겨두는 것이 좋습니다.

이렇게 하드웨어를 구성하면 BLE를 통해 제어 가능한 기본적인 스마트 잠금장치의 틀이 완성됩니다. 다음 섹션에서는 이 하드웨어를 제어하기 위한 소프트웨어 구현에 대해 자세히 알아보겠습니다.

4. ESP32 BLE 서버 구현 💻

이제 하드웨어 설계가 완료되었으니, ESP32를 BLE 서버로 구현하여 스마트폰 앱에서 잠금장치를 제어할 수 있도록 만들어보겠습니다. 이 섹션에서는 ESP32에서 BLE 서버를 구현하는 방법과 필요한 서비스 및 특성을 정의하는 과정을 상세히 설명하겠습니다.

4.1 BLE 서버 기본 구조

ESP32에서 BLE 서버를 구현하기 위해 다음과 같은 기본 구조를 따릅니다:

  1. BLE 디바이스 초기화
  2. 서비스 생성
  3. 특성 정의
  4. 광고 시작
  5. 연결 대기 및 요청 처리

4.2 필요한 라이브러리 및 헤더 파일

먼저, 필요한 라이브러리와 헤더 파일을 포함시킵니다:

#include <BLEDevice.h>
#include <BLEUtils.h>
#include <BLEServer.h>
#include <BLE2902.h>

#include <ESP32Servo.h>

4.3 UUID 정의

서비스와 특성을 식별하기 위한 UUID를 정의합니다:

#define SERVICE_UUID        "4fafc201-1fb5-459e-8fcc-c5c9c331914b"
#define CHARACTERISTIC_UUID "beb5483e-36e1-4688-b7f5-ea07361b26a8"

4.4 전역 변수 선언

BLEServer* pServer = NULL;
BLECharacteristic* pCharacteristic = NULL;
bool deviceConnected = false;
bool oldDeviceConnected = false;
uint32_t value = 0;

Servo lockServo;
const int servoPin = 13;
const int ledPin = 12;
const int buttonPin = 14;
bool isLocked = true;

4.5 콜백 클래스 정의

BLE 연결 상태 변경을 처리하기 위한 콜백 클래스를 정의합니다:

class MyServerCallbacks: public BLEServerCallbacks {
    void onConnect(BLEServer* pServer) {
      deviceConnected = true;
    };

    void onDisconnect(BLEServer* pServer) {
      deviceConnected = false;
    }
};

4.6 setup() 함수 구현

setup() 함수에서 BLE 서버를 초기화하고 서비스 및 특성을 생성합니다:

void setup() {
  Serial.begin(115200);
  
  // 서보 모터 초기화
  ESP32PWM::allocateTimer(0);
  lockServo.setPeriodHertz(50);
  lockServo.attach(servoPin, 500, 2400);
  
  // LED 및 버튼 핀 설정
  pinMode(ledPin, OUTPUT);
  pinMode(buttonPin, INPUT_PULLUP);
  
  // BLE 디바이스 생성
  BLEDevice::init("ESP32 Smart Lock");
  
  // BLE 서버 생성
  pServer = BLEDevice::createServer();
  pServer->setCallbacks(new MyServerCall  backs());

  // BLE 서비스 생성
  BLEService *pService = pServer->createService(SERVICE_UUID);

  // BLE 특성 생성
  pCharacteristic = pService->createCharacteristic(
                      CHARACTERISTIC_UUID,
                      BLECharacteristic::PROPERTY_READ   |
                      BLECharacteristic::PROPERTY_WRITE  |
                      BLECharacteristic::PROPERTY_NOTIFY |
                      BLECharacteristic::PROPERTY_INDICATE
                    );

  // BLE 디스크립터 추가
  pCharacteristic->addDescriptor(new BLE2902());

  // 서비스 시작
  pService->start();

  // 광고 시작
  BLEAdvertising *pAdvertising = BLEDevice::getAdvertising();
  pAdvertising->addServiceUUID(SERVICE_UUID);
  pAdvertising->setScanResponse(false);
  pAdvertising->setMinPreferred(0x0);  // iPhone 연결 문제 해결을 위한 설정
  BLEDevice::startAdvertising();
  
  Serial.println("BLE 서버 준비 완료. 연결을 기다리는 중...");
}

4.7 loop() 함수 구현

loop() 함수에서는 연결 상태를 확인하고, 잠금 상태를 제어합니다:

void loop() {
  // 연결 상태 변경 확인
  if (deviceConnected && !oldDeviceConnected) {
    oldDeviceConnected = deviceConnected;
    Serial.println("디바이스가 연결되었습니다.");
  }
  if (!deviceConnected && oldDeviceConnected) {
    delay(500); // Bluetooth 스택이 준비되기를 기다립니다.
    pServer->startAdvertising(); // 광고 재시작
    Serial.println("광고를 시작합니다.");
    oldDeviceConnected = deviceConnected;
  }

  // 버튼 상태 확인
  if (digitalRead(buttonPin) == LOW) {
    toggleLock();
    delay(200); // 디바운싱
  }

  // BLE 특성 값 읽기
  if (deviceConnected) {
    std::string value = pCharacteristic->getValue();
    if (value == "UNLOCK") {
      unlockDoor();
    } else if (value == "LOCK") {
      lockDoor();
    }
  }

  delay(10); // 짧은 지연으로 안정성 향상
}

4.8 잠금 제어 함수 구현

도어락을 제어하기 위한 함수들을 구현합니다:

void toggleLock() {
  if (isLocked) {
    unlockDoor();
  } else {
    lockDoor();
  }
}

void unlockDoor() {
  lockServo.write(0); // 서보 모터를 0도 위치로 이동
  digitalWrite(ledPin, HIGH); // LED 켜기
  isLocked = false;
  Serial.println("문이 열렸습니다.");
  if (deviceConnected) {
    pCharacteristic->setValue("UNLOCKED");
    pCharacteristic->notify();
  }
}

void lockDoor() {
  lockServo.write(90); // 서보 모터를 90도 위치로 이동
  digitalWrite(ledPin, LOW); // LED 끄기
  isLocked = true;
  Serial.println("문이 잠겼습니다.");
  if (deviceConnected) {
    pCharacteristic->setValue("LOCKED");
    pCharacteristic->notify();
  }
}

4.9 보안 고려사항

실제 제품 개발 시에는 다음과 같은 보안 사항을 고려해야 합니다:

  • 암호화: BLE 통신을 암호화하여 중간자 공격을 방지합니다.
  • 인증: 사용자 인증 메커니즘을 구현하여 무단 접근을 차단합니다.
  • 페어링: 안전한 페어링 과정을 구현하여 신뢰할 수 있는 기기만 연결되도록 합니다.
  • 타임아웃: 일정 시간 동안 활동이 없으면 자동으로 연결을 해제합니다.

4.10 전력 관리

배터리 수명을 연장하기 위해 다음과 같은 전력 관리 기법을 적용할 수 있습니다:

  • 연결이 없을 때 ESP32를 딥 슬립 모드로 전환
  • BLE 광고 간격 조정
  • 불필요한 기능을 비활성화하여 전력 소비 최소화

이렇게 구현된 ESP32 BLE 서버는 스마트폰 앱에서 연결하여 도어락을 원격으로 제어할 수 있게 해줍니다. 다음 섹션에서는 이 BLE 서버와 통신할 수 있는 모바일 앱 개발에 대해 알아보겠습니다.

5. 모바일 앱 개발 (Android) 📱

이제 ESP32 BLE 서버와 통신할 수 있는 Android 앱을 개발해보겠습니다. 이 앱은 사용자가 스마트폰으로 도어락을 제어할 수 있게 해줍니다. 여기서는 Android Studio를 사용하여 앱을 개발하는 과정을 설명하겠습니다.

5.1 개발 환경 설정

  1. Android Studio 설치
  2. 새 프로젝트 생성 (최소 SDK: API 21 이상 선택)
  3. 필요한 권한 추가 (AndroidManifest.xml):
    <uses-permission android:name="android.permission.BLUETOOTH"/>
    <uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
    <uses-feature android:name="android.hardware.bluetooth_le" android:required="true"/>

5.2 사용자 인터페이스 설계

activity_main.xml 파일에 다음과 같은 UI 요소를 추가합니다:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:padding="16dp">

    <Button
        android:id="@+id/btnScan"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="스캔 시작" />

    <ListView
        android:id="@+id/deviceList"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1" />

    <Button
        android:id="@+id/btnLock"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="잠금"
        android:enabled="false" />

    <Button
        android:id="@+id/btnUnlock"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="잠금 해제"
        android:enabled="false" />

</LinearLayout>

5.3 BLE 스캔 구현

MainActivity.kt 파일에 BLE 스캔 기능을 구현합니다:

import android.bluetooth.BluetoothAdapter
import android.bluetooth.BluetoothManager
import android.bluetooth.le.ScanCallback
import android.bluetooth.le.ScanResult
import android.content.Context
import android.os.Bundle
import android.widget.ArrayAdapter
import android.widget.Button
import android.widget.ListView
import androidx.appcompat.app.AppCompatActivity

class MainActivity : AppCompatActivity() {

    private lateinit var bluetoothAdapter: BluetoothAdapter
    private lateinit var deviceListAdapter: ArrayAdapter<String>
    private val deviceList = ArrayList<String>()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val bluetoothManager = getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager
        bluetoothAdapter = bluetoothManager.adapter

        deviceListAdapter = ArrayAdapter(this, android.R.layout.simple_list_item_1, deviceList)
        findViewById<ListView>(R.id.deviceList).adapter = deviceListAdapter

        findViewById<Button>(R.id.btnScan).setOnClickListener {
            startScan()
        }
    }

    private fun startScan() {
        bluetoothAdapter.bluetoothLeScanner.startScan(scanCallback)
    }

    private val scanCallback = object : ScanCallback() {
        override fun onScanResult(callbackType: Int, result: ScanResult) {
            val deviceAddress = result.device.address
            val deviceName = result.device.name ?: "Unknown Device"
            val deviceInfo = "$deviceName ($deviceAddress)"
            if (!deviceList.contains(deviceInfo)) {
                deviceList.add(deviceInfo)
                deviceListAdapter.notifyDataSetChanged()
            }
        }
    }
}

5.4 BLE 연결 및 통신 구현

선택한 디바이스에 연결하고 통신하는 기능을 구현합니다:

import android.bluetooth.BluetoothGatt
import android.bluetooth.BluetoothGattCallback
import android.bluetooth.BluetoothGattCharacteristic
import android.bluetooth.BluetoothProfile

class MainActivity : AppCompatActivity() {
    // ... 이전 코드 ...

    private var bluetoothGatt: BluetoothGatt? = null
    private var lockCharacteristic: BluetoothGattCharacteristic? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        // ... 이전 코드 ...

        findViewById<ListView>(R.id.deviceList).setOnItemClickListener { _, _, position, _ ->
            val deviceInfo = deviceList[position]
            val deviceAddress = deviceInfo.split("(")[1].replace(")", "")
            connectToDevice(deviceAddress)
        }

        findViewById<Button>(R.id.btnLock).setOnClickListener {
            sendCommand("LOCK")
        }

        findViewById<Button>(R.id.btnUnlock).setOnClickListener {
            sendCommand("UNLOCK")
        }
    }

    private fun connectToDevice(address: String) {
        val device = bluetoothAdapter.getRemoteDevice(address)
        bluetoothGatt = device.connectGatt(this, false, gattCallback)
    }

    private val gattCallback = object : BluetoothGattCallback() {
        override fun onConnectionStateChange(gatt: BluetoothGatt, status: Int, newState: Int) {
            if (newState == BluetoothProfile.STATE_CONNECTED) {
                runOnUiThread {
                    findViewById<Button>(R.id.btnLock).isEnabled = true
                    findViewById<Button>(R.id.btnUnlock).isEnabled = true
                }
                gatt.discoverServices()
            }
        }

        override fun onServicesDiscovered(gatt: BluetoothGatt, status: Int) {
            if (status == BluetoothGatt.GATT_SUCCESS) {
                val service = gatt.getService(UUID.fromString(SERVICE_UUID))
                lockCharacteristic = service.getCharacteristic(UUID.fromString(CHARACTERISTIC_UUID))
            }
        }

        override fun onCharacteristicChanged(gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic) {
            val value = characteristic.getStringValue(0)
            runOnUiThread {
                // 상태 업데이트 처리
            }
        }
    }

    private fun sendCommand(command: String) {
        lockCharacteristic?.let { characteristic ->
            characteristic.setValue(command)
            bluetoothGatt?.writeCharacteristic(characteristic)
        }
    }

    companion object {
        const val SERVICE_UUID = "4fafc201-1fb5-459e-8fcc-c5c9c331914b"
        const val CHARACTERISTIC_UUID = "beb5483e-36e1-4688-b7f5-ea07361b26a8"
    }
}

5.5 사용자 인증 구현

보안을 강화하기 위해 간단한 사용자 인증 기능을 추가할 수 있습니다:

import android.content.SharedPreferences

class MainActivity : AppCompatActivity() {
    // ... 이전 코드 ...

    private lateinit var sharedPreferences: SharedPreferences

    override fun onCreate(savedInstanceState: Bundle?) {
        // ... 이전 코드 ...

        sharedPreferences = getSharedPreferences("SmartLockPrefs", Context.MODE_PRIVATE)

        if (!isUserAuthenticated()) {
            showAuthenticationDialog()
        }
    }

    private fun isUserAuthenticated(): Boolean {
        return sharedPreferences.getBoolean("isAuthenticated", false)
    }

    private fun showAuthenticationDialog() {
        val builder = AlertDialog.Builder(this)
        val input = EditText(this)
        builder.setTitle("인증")
            .setMessage("PIN을 입력하세요:")
            .setView(input)
            .setPositiveButton("확인") { _, _ ->
                val pin = input.text.toString()
                if (pin == "1234") { // 실제 앱에서는 더 안전한 인증 방식 사용
                    sharedPreferences.edit().putBoolean("isAuthenticated", true).apply()
                } else {
                    Toast.makeText(this, "인증 실패", Toast.LENGTH_SHORT).show()
                    finish()
                }
            }
            .setNegativeButton("취소") { _, _ ->
                finish()
            }
            .setCancelable(false)
            .show()
    }
}

5.6 에러 처리 및 사용자 피드백

사용자에게 적절한 피드백을 제공하기 위해 에러 처리와 상태 업데이트 기능을 구현합니다:

class MainActivity : AppCompatActivity() {
    // ... 이전 코드 ...

    private fun updateStatus(status: String) {
        runOnUiThread {
            Toast.makeText(this, status, Toast.LENGTH_SHORT).show()
        }
    }

    private val gattCallback = object : BluetoothGattCallback() {
        // ... 이전 코드 ...

        override fun onCharacteristicWrite(gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic, status: Int) {
            if (status == BluetoothGatt.GATT_SUCCESS) {
                updateStatus("명령이 성공적으로 전송되었습니다.")
            } else {
                updateStatus("명령 전송 실패")
            }
        }

        override fun onCharacteristicChanged(gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic) {
            val value = characteristic.getStringValue(0)
            updateStatus("도어 상태: $value")
        }
    }

    override fun onDestroy() {
        super.onDestroy()
        bluetoothGatt?.close()
    }
}

5.7 배터리 최적화

앱의 배터리 사용량을 최적화하기 위해 다음과 같은 방법을 적용할 수 있습니다:

  • BLE 스캔 시간 제한 설정
  • 백그라운드에서 불필요한 BLE 연결 해제
  • 주기적인 연결 대신 필요할 때만 연결하는 방식 사용

5.8 추가 기능 구현

앱의 기능을 확장하기 위해 다음과 같은 기능을 추가로 구현할 수 있습니다:

  • 잠금/해제 이력 관리
  • 다중 사용자 지원
  • 원격 접근 제어 (인터넷 연결 필요)
  • 푸시 알림 기능

이렇게 개발된 Android 앱은 ESP32 기반의 스마트 잠금장치와 원활하게 통신하여 사용자가 스마트폰으로 도어락을 제어할 수 있게 해줍니다. 실제 제품화를 위해서는 더 강력한 보안 기능과 사용자 경험 개선, 그리고 다양한 플랫폼 지원 (iOS 등) 등이 추가로 필요할 것입니다.

6. 보안 강화 및 최적화 🛡️

스마트 잠금장치는 보안과 직결되는 제품이므로, 강력한 보안 기능이 필수적입니다. 또한, 배터리로 작동하는 IoT 기기이므로 전력 소비를 최소화하는 것도 중요합니다. 이 섹션에서는 프로젝트의 보안을 강화하고 성능을 최적화하는 방법에 대해 알아보겠습니다.

6.1 BLE 보안 강화

  1. 페어링 및 본딩:
    // ESP32 코드
    BLEDevice::setEncryptionLevel(ESP_BLE_SEC_ENCRYPT);
    BLEDevice::setSecurityCallbacks(new MySecurity());
  2. 암호화: AES-128 암호화 사용
    // ESP32 코드
    BLESecurity *pSecurity = new BLESecurity();
    pSecurity->setAuthenticationMode(ESP_LE_AUTH_REQ_SC_BOND);
    pSecurity->setCapability(ESP_IO_CAP_NONE);
    pSecurity->setInitEncryptionKey(ESP_BLE_ENC_KEY_MASK | ESP_BLE_ID_KEY_MASK);
  3. MAC 주소 랜덤화:
    // ESP32 코드
    esp_ble_gap_config_local_privacy(true);

6.2 인증 및 권한 관리

  1. 사용자 인증: JWT(JSON Web Tokens) 사용
    // Android 코드
    implementation 'com.auth0.android:jwtdecode:2.0.0'
    
    // JWT 생성 (서버 사이드)
    String token = JWT.create()
        .withIssuer("smart-lock")
        .withClaim("user_id", userId)
        .withExpiresAt(new Date(System.currentTimeMillis() + 3600000))
        .sign(Algorithm.HMAC256(secretKey));
    
    // JWT 검증 (Android)
    try {
        JWT jwt = JWT.decode(token);
        boolean isExpired = jwt.getExpiresAt().before(new Date());
        if (!isExpired) {
            // 토큰이 유효함
        }
    } catch (JWTDecodeException exception) {
        // 잘못된 토큰
    }
  2. 권한 관리: 역할 기반 접근 제어(RBAC) 구현
    // 권한 정의
    enum class UserRole {
        OWNER,
        FAMILY_MEMBER,
        GUEST
    }
    
    // 권한 검사
    fun checkPermission(user: User, action: Action): Boolean {
        return when (user.role) {
            UserRole.OWNER -> true
            UserRole.FAMILY_MEMBER -> action != Action.MANAGE_USERS
            UserRole.GUEST -> action == Action.UNLOCK
        }
    }

6.3 데이터 보안

  1. 안전한 키 저장: Android Keystore 시스템 사용
    import android.security.keystore.KeyGenParameterSpec
    import android.security.keystore.KeyProperties
    import java.security.KeyStore
    
    val keyStore = KeyStore.getInstance("AndroidKeyStore").apply { load(null) }
    
    val keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore")
    val keyGenParameterSpec = KeyGenParameterSpec.Builder("MyKeyAlias",
        KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT)
        .setBlockModes(KeyProperties.BLOCK_MODE_CBC)
        .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7)
        .build()
    
    keyGenerator.init(keyGenParameterSpec)
    keyGenerator.generateKey()
  2. 안전한 통신: HTTPS 사용 (원격 접근 시)
    // OkHttp 라이브러리 사용
    val client = OkHttpClient.Builder()
        .connectTimeout(10, TimeUnit.SECONDS)
        .writeTimeout(10, TimeUnit.SECONDS)
        .readTimeout(30, TimeUnit.SECONDS)
        .build()
    
    val request = Request.Builder()
        .url("https://api.example.com/smart-lock/status")
        .build()
    
    client.newCall(request).enqueue(object : Callback {
        override fun onFailure(call: Call, e: IOException) {
            // 에러 처리
        }
    
        override fun onResponse(call: Call, response: Response) {
            // 응답 처리
        }
    })

6.4 전력 소비 최적화

  1. ESP32 딥 슬립 모드 사용:
    #define uS_TO_S_FACTOR 1000000  // 마이크로초를 초로 변환
    #define TIME_TO_SLEEP  5        // ESP32가 5초 동안 슬립 모드로 진입
    
    void goToDeepSleep(){
      esp_sleep_enable_timer_wakeup(TIME_TO_SLEEP * uS_TO_S_FACTOR);
      Serial.println("Going to deep sleep now");
      esp_deep_sleep_start();
    }
  2. BLE 광고 간격 조정:
    BLEAdvertising *pAdvertising = pServer->getAdvertising();
    pAdvertising->setMinInterval(0x20); // 20ms
    pAdvertising->setMaxInterval(0x40); // 40ms
  3. 안드로이드 앱 배터리 최적화:
    // WorkManager를 사용한 백그라운드 작업 스케줄링
    val constraints = Constraints.Builder()
        .setRequiresBatteryNotLow(true)
        .build()
    
    val workRequest = PeriodicWorkRequestBuilder(15, TimeUnit.MINUTES)
        .setConstraints(constraints)
        .build()
    
    WorkManager.getInstance(context).enqueueUniquePeriodicWork(
        "lockStatusCheck",
        ExistingPeriodicWorkPolicy.REPLACE,
        workRequest
    )

6.5 오류 처리 및 복구

  1. 연결 재시도 메커니즘:
    private fun connectWithRetry(device: BluetoothDevice, maxAttempts: Int = 3) {
        var attempts = 0
        fun attemptConnection() {
            if (attempts >= maxAttempts) {
                updateStatus("연결 실패: 최대 시도 횟수 초과")
                return
            }
            attempts++
            try {
                bluetoothGatt = device.connectGatt(this, false, gattCallback)
            } catch (e: Exception) {
                updateStatus("연결 오류: ${e.message}")
                Handler(Looper.getMainLooper()).postDelayed({ attemptConnection() }, 5000)
            }
        }
        attemptConnection()
    }
  2. 워치독 타이머: ESP32에서 시스템 안정성 확보
    #include "esp_system.h"
    
    const int watchdogTimeout = 30; // 30초
    
    void setupWatchdog() {
        esp_task_wdt_init(watchdogTimeout, true); //watchdog 초기화
        esp_task_wdt_add(NULL); //현재 작업을 watchdog에 추가
    }
    
    void loop() {
        esp_task_wdt_reset(); //watchdog 타이머 리셋
        // 메인 로직
    }

6.6 펌웨어 업데이트 (OTA)

ESP32에 OTA(Over-The-Air) 업데이트 기능을 구현하여 원격으로 펌웨어를 업데이트할 수 있습니다:

#include 

void setupOTA() {
  ArduinoOTA.setHostname("ESP32_SmartLock");
  ArduinoOTA.setPassword("admin123");
  
  ArduinoOTA.onStart([]() {
    String type;
    if (ArduinoOTA.getCommand() == U_FLASH)
      type = "sketch";
    else
      type = "filesystem";
    Serial.println("Start updating " + type);
  });
  
  ArduinoOTA.onEnd([]() {
    Serial.println("\nEnd");
  });
  
  ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) {
    Serial.printf("Progress: %u%%\r", (progress / (total / 100)));
  });
  
  ArduinoOTA.onError([](ota_error_t error) {
    Serial.printf("Error[%u]: ", error);
    if (error == OTA_AUTH_ERROR) Serial.println("Auth Failed");
    else if (error == OTA_BEGIN_ERROR) Serial.println("Begin Failed");
    else if (error == OTA_CONNECT_ERROR) Serial.println("Connect Failed");
    else if (error == OTA_RECEIVE_ERROR) Serial.println("Receive Failed");
    else if (error == OTA_END_ERROR) Serial.println("End Failed");
  });
  
  ArduinoOTA.begin();
}

void loop() {
  ArduinoOTA.handle();
  // 기타 로직
}

이러한 보안 강화 및 최적화 기법들을 적용함으로써, 스마트 잠금장치의 안전성과 효율성을 크게 향상시킬 수 있습니다. 실제 제품 개발 시에는 이러한 기본적인 방법들 외에도 더욱 고도화된 보안 기술과 최적화 기법을 적용해야 하며, 지속적인 보안 감사와 업데이트가 필요합니다.

관련 키워드

  • ESP32
  • Bluetooth Low Energy (BLE)
  • 스마트 잠금장치
  • IoT
  • 안드로이드 앱 개발
  • 임베디드 시스템
  • 보안
  • 전력 최적화
  • 사용자 인증
  • 펌웨어 개발

지식의 가치와 지적 재산권 보호

자유 결제 서비스

'지식인의 숲'은 "이용자 자유 결제 서비스"를 통해 지식의 가치를 공유합니다. 콘텐츠를 경험하신 후, 아래 안내에 따라 자유롭게 결제해 주세요.

자유 결제 : 국민은행 420401-04-167940 (주)재능넷
결제금액: 귀하가 받은 가치만큼 자유롭게 결정해 주세요
결제기간: 기한 없이 언제든 편한 시기에 결제 가능합니다

지적 재산권 보호 고지

  1. 저작권 및 소유권: 본 컨텐츠는 재능넷의 독점 AI 기술로 생성되었으며, 대한민국 저작권법 및 국제 저작권 협약에 의해 보호됩니다.
  2. AI 생성 컨텐츠의 법적 지위: 본 AI 생성 컨텐츠는 재능넷의 지적 창작물로 인정되며, 관련 법규에 따라 저작권 보호를 받습니다.
  3. 사용 제한: 재능넷의 명시적 서면 동의 없이 본 컨텐츠를 복제, 수정, 배포, 또는 상업적으로 활용하는 행위는 엄격히 금지됩니다.
  4. 데이터 수집 금지: 본 컨텐츠에 대한 무단 스크래핑, 크롤링, 및 자동화된 데이터 수집은 법적 제재의 대상이 됩니다.
  5. AI 학습 제한: 재능넷의 AI 생성 컨텐츠를 타 AI 모델 학습에 무단 사용하는 행위는 금지되며, 이는 지적 재산권 침해로 간주됩니다.

재능넷은 최신 AI 기술과 법률에 기반하여 자사의 지적 재산권을 적극적으로 보호하며,
무단 사용 및 침해 행위에 대해 법적 대응을 할 권리를 보유합니다.

© 2024 재능넷 | All rights reserved.

댓글 작성
0/2000

댓글 0개

해당 지식과 관련있는 인기재능

#### 바로 구매하지 마시고 쪽지 문의 후 구매해 주세요 #### (프로그램 요구사양 문서 (PPT,한글,워드등 양식은 상관없습니다.)C언어/C++언어 자...

  >> 결제 전 쪽지 상담 먼저 부탁드립니다!! <<>> 결제 전 쪽지 상담 먼저 부탁드립니다!! <<​  한...

안녕하세요. 올해로 개발경력 13년차입니다.Windows Device Driver 부터 시작해서, Windows/Linux Programming, Mobile Programming(iOS/Android),...

📚 생성된 총 지식 2,796 개

  • (주)재능넷 | 대표 : 강정수 | 경기도 수원시 영통구 봉영로 1612, 7층 710-09 호 (영통동) | 사업자등록번호 : 131-86-65451
    통신판매업신고 : 2018-수원영통-0307 | 직업정보제공사업 신고번호 : 중부청 2013-4호 | jaenung@jaenung.net

    (주)재능넷의 사전 서면 동의 없이 재능넷사이트의 일체의 정보, 콘텐츠 및 UI등을 상업적 목적으로 전재, 전송, 스크래핑 등 무단 사용할 수 없습니다.
    (주)재능넷은 통신판매중개자로서 재능넷의 거래당사자가 아니며, 판매자가 등록한 상품정보 및 거래에 대해 재능넷은 일체 책임을 지지 않습니다.

    Copyright © 2024 재능넷 Inc. All rights reserved.
ICT Innovation 대상
미래창조과학부장관 표창
서울특별시
공유기업 지정
한국데이터베이스진흥원
콘텐츠 제공서비스 품질인증
대한민국 중소 중견기업
혁신대상 중소기업청장상
인터넷에코어워드
일자리창출 분야 대상
웹어워드코리아
인터넷 서비스분야 우수상
정보통신산업진흥원장
정부유공 표창장
미래창조과학부
ICT지원사업 선정
기술혁신
벤처기업 확인
기술개발
기업부설 연구소 인정
마이크로소프트
BizsPark 스타트업
대한민국 미래경영대상
재능마켓 부문 수상
대한민국 중소기업인 대회
중소기업중앙회장 표창
국회 중소벤처기업위원회
위원장 표창