본문 바로가기

ESP32

ESP32 BLE 서버 만들기 가이드와 코드 첨부

반응형

위 영상에서 언급된 코드는 아래와 같습니다. 코드 아래에 본문이 이어집니다. 

 

/*
  TODO

    [x] Create a BLE Device
        [x] Import BLEDevice Library
        [x] Name Device
        [x] Initialize Device

    [x] Create Server
        [x] Import BLEServer library
        [x] Create BLEServer

    [x] Create Server Callbacks
        [x] OnConnect
            [x] Turn on LED
        [x] OnDisconnect
            [x] Turn off LED
    
    [x] Create Service
        [x] Define a Service UUID
        [x] Create a Service
        [x] Start the Service

    [x] Create Characteristic
        [x] Define a Characteristic UUID
        [x] Create a Characteristic
        [x] Add Characteristic to Service

    [x] Create a Characteristic Callback
        [x] create sub-class to override BLECharacteristicCallbacks
        [x] Send millis value every time request comes in

    [x] Create a Descriptor
        [x] Add a Descriptor (2901)

    [] Advertise the Service
        [x] Get the Advertising object from Device
        [x] Create and set Advertisement Data
        [x] Create and set Scan Data
        [x] Add Advertisement and Scan Data to Advertising Object
        [x] Add Service UUID
        [x] Start Advertising

*/

/** Includes ***********************************/
#include 
#include 

/** Defines ***********************************/
#define DEVICE_NAME "CIA Monitoring Device"
// See the following for generating UUIDs:
// https://www.uuidgenerator.net/
#define SERVICE_1_UUID "d9b4049b-c663-446c-9211-71754d930811"
#define CHARACTERISTIC_1A_UUID "596d9fe5-baf0-472b-89ee-bbb3ce27165a"
#define CHARACTERISTIC_1B_UUID "fd52c152-7b33-4059-80c6-090143a3d2f8"

/** Callbacks ***********************************/
class MyServerCallbacks : public BLEServerCallbacks {

  void onConnect(BLEServer *pServer) {
    digitalWrite(2, HIGH);
    Serial.println("Client Connected");
  }

  void onDisconnect(BLEServer *pServer) {
    digitalWrite(2, LOW);
    Serial.println("Client Disconnected");
    BLEDevice::startAdvertising();
  }
};

class MyCharacteristic_1A_Callbacks : public BLECharacteristicCallbacks {

  void onRead(BLECharacteristic *pCharacteristic) {
    uint32_t currentMillis = millis() / 1000;
    pCharacteristic->setValue(currentMillis);
  }
};



void setup() {

  Serial.begin(9600);
  Serial.println("ESP32 BLE Server setup beginning...");

  // Pin modes
  pinMode(2, OUTPUT);

  //Initialize Device
  BLEDevice::init(DEVICE_NAME);

  // Create Server
  BLEServer *pServer = BLEDevice::createServer();
  pServer->setCallbacks(new MyServerCallbacks());

  // Services
  BLEService *pService = pServer->createService(SERVICE_1_UUID);

  // Characteristics
  BLECharacteristic *pCharacteristic_1A = pService->createCharacteristic(
    CHARACTERISTIC_1A_UUID,
    BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_WRITE);

  pCharacteristic_1A->setCallbacks(new MyCharacteristic_1A_Callbacks());

  // Descriptors
  BLE2901 *pDescriptor_2901 = new BLE2901();
  pDescriptor_2901->setDescription("Time");
  pCharacteristic_1A->addDescriptor(pDescriptor_2901);

  pService->start();

  // Advertising
  // Get the Advertising object
  BLEAdvertising *pAdvertising = BLEDevice::getAdvertising();

  /*
  // Advertisement Data
  BLEAdvertisementData advertisementData;
  advertisementData.setFlags(ESP_BLE_ADV_FLAG_GEN_DISC | ESP_BLE_ADV_FLAG_BREDR_NOT_SPT);
  advertisementData.setName(DEVICE_NAME);
  advertisementData.setCompleteServices(BLEUUID(SERVICE_1_UUID));
  advertisementData.setPartialServices(BLEUUID(SERVICE_1_UUID));
  advertisementData.setServiceData(BLEUUID(SERVICE_1_UUID), "some data");
  
  // // Scan Response Data
  BLEAdvertisementData scanResponseData;
  scanResponseData.setName(DEVICE_NAME);
  
  // Add advertising data and scan response data to advertising
  pAdvertising->setAdvertisementData(advertisementData);
  pAdvertising->setScanResponseData(scanResponseData);
  */
  
  // Adertising settings
  pAdvertising->addServiceUUID(SERVICE_1_UUID);
  pAdvertising->setScanResponse(true);
  pAdvertising->setMinPreferred(0x06);  // functions that help with iPhone connections issue

  BLEDevice::startAdvertising();
}

void loop() {
  // put your main dog chow here, to run repeatedly:
}

 

 

BLE 기술은 ESP32의 저렴하고 저전력이라는 장점을 활용하여 완전히 새로운 가능성을 열어주는 흥미로운 기술입니다! 아두이노 플랫폼을 사용하면 ESP32 장치를 BLE 클라이언트 또는 BLE 서버로 작동하도록 설정할 수 있습니다.

 

이 글에서는 ESP32를 BLE 서버로 사용하는 방법에 대해 알아보겠습니다. 그럼 이제 이 기술이 무엇인지 자세히 살펴보겠습니다!

 

https://linuxhint.com/esp32-bluetooth-low-energy-ble-arduino-ide/

 

먼저 이 글에 필요한 몇 가지 핵심 개념을 정의해 보겠습니다.

 

BLE는 Bluetooth Low Energy 의 약자입니다 . 이름에서 알 수 있듯이 BLE는 기존 블루투스 기술의 전력 소모를 줄인 버전 입니다. 블루투스 기술은 개인 영역 네트워크(PAN) 에서 단거리 무선 통신에 사용되는 기술입니다 .

 

스마트워치를 스마트폰에 연결하거나 스마트폰으로 호텔 객실에 디지털 키를 사용하여 출입할 때 BLE 기술을 가장 익숙하게 느끼실 겁니다. BLE 기술은 이제 우리 일상생활 곳곳에 거의 보편화되었습니다!

 

BLE 비콘 스캐너 관련 글에서 ESP32가 BLE 기술을 사용하여 주변 기기에 자신의 존재를 주기적으로 알리는 비콘 또는 라이트하우스 역할을 간단하게 수행할 수 있는 방법을 설명했습니다. 이러한 활용 방식은 근접 알림, 근접 인식 등이 필요한 애플리케이션을 개발하는 데 매우 적합합니다.

 

이번 글에서는 그 개념을 좀 더 발전시켜 보겠습니다. ESP32와 아두이노에 BLE 기술을 활용하여 기기들이 서로 통신할 수 있는 클라이언트-서버 방식의 아키텍처를 구축할 수 있습니다!

 

ESP32 BLE 서버는 다른 BLE 장치에 자신을 알리고, ESP32 BLE 클라이언트는 주변 장치를 스캔하여 ESP32 BLE 서버를 찾습니다. ESP32 BLE 클라이언트가 BLE 서버에 연결하면, ESP32 BLE 서버는 BLE 클라이언트로 데이터 전송을 시작합니다.

 

이러한 유형의 통신을 흔히 지점 간 통신 이라고 합니다 . 또는 위에서 언급했듯이 개인 영역 네트워크라고도 합니다.

 

아래 코드에서는 클라이언트-서버 아키텍처의 ESP32 BLE 서버 부분에 초점을 맞춰 구현해 보겠습니다.

 

아래 예시 스케치의 프로세스 흐름은 다음과 같습니다…

  1. ESP32 BLE 서버 프로그램을 작성하고 ESP32에 업로드하세요.
  2. ESP32 BLE 클라이언트 프로그램을 생성하고 여러 ESP32에 업로드하세요.
  3. ESP32 BLE 서버에 ESP32 BLE 클라이언트를 연결합니다.
  4. ESP32 BLE 서버에서 ESP32 BLE 클라이언트로 데이터를 전송합니다.

 

이 글을 끝까지 읽으시면 ESP32 장치를 사용하여 BLE 클라이언트 서버 프로젝트를 성공적으로 만들 수 있게 될 것입니다. 자, 시작해 볼까요!

 

참고: ESP32를 BLE 클라이언트로 사용하는 방법에 대한 기사도 있습니다.

 

ESP32 BLE 서버 테스트 코드

 

다음은 ESP32를 사용하여 BLE 서버를 생성하고 사용하는 전체 예제 코드입니다. 아래 섹션에서는 이 코드를 단계별로 함께 살펴보겠습니다.

 

최종 결과물을 바로 확인하고 싶다면, 이 스케치를 ESP32에 업로드하고 지금 바로 실행해 보세요!

 

//  BLE Server Example Sketch
//
//  Programming Electronics Academy
//

#include 
#include 
#include 

// See the following for generating UUIDs:
// https://www.uuidgenerator.net/

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

void setup() {

  Serial.begin(115200);
  Serial.println("Starting BLE server setup!");
  BLEDevice::init("PEA - BLE Server Test");
  BLEServer *pServer = BLEDevice::createServer();
  BLEService *pService = pServer->createService(SERVICE_UUID);
  BLECharacteristic *pCharacteristic = pService->createCharacteristic(

                                         CHARACTERISTIC_UUID,
                                         BLECharacteristic::PROPERTY_READ |
                                         BLECharacteristic::PROPERTY_WRITE

                                       );

  pCharacteristic->setValue("We love Programming Electronics Academy");
  pService->start();
  BLEAdvertising *pAdvertising = BLEDevice::getAdvertising();
  pAdvertising->addServiceUUID(SERVICE_UUID);
  pAdvertising->setScanResponse(true);
  pAdvertising->setMinPreferred(0x06);  
  pAdvertising->setMinPreferred(0x12);
  BLEDevice::startAdvertising();
  Serial.println("Characteristic defined!");

}

void loop() {

  // put your main code here, to run repeatedly:
  delay(2000);

}

 

 

ESP32 BLE 서버 코드 둘러보기

 

아두이노 개발 환경의 가장 큰 장점 중 하나는 사용할 수 있는 훌륭한 오픈 소스 라이브러리가 매우 많다는 것입니다!

 

프로그램 시작 부분에서는 BLE 장치, 유틸리티 및 서버 라이브러리를 포함하여 이러한 라이브러리 중 일부를 활용할 것입니다.

 

그러므로 "BLEdevice", "BLEUtils" 또는 "BLEServer"라는 참조를 볼 때마다 해당 라이브러리가 코드 전체에서 언제 어디서 사용되는지 알 수 있다는 점을 기억하세요.

 

그리고 이는 아두이노 플랫폼이 얼마나 훌륭한지 다시 한번 상기시켜주는 것입니다. 바로 다른 개발자들이 공유한 라이브러리를 활용할 수 있다는 점이죠!

 

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

 

 

이제 우리가 만들 BLE 서버에 대한 몇 가지 고유 식별자를 정의해야 합니다. BLE 장치는 종종 다른 식별 특성과 함께 장치에 대한 고유 식별자(UUID)를 브로드캐스트합니다.

 

UUID는 범용 고유 식별자입니다. 장치 서비스와 장치 특성에 각각 UUID가 있습니다. BLE 아키텍처는 계층적 구조로 정의되어 있습니다. 따라서 BLE 장치는 서비스를 가지고 있으며, 이러한 서비스는 특성을 가집니다. 장치가 지원하는 각 서비스와 각 특성에는 고유한 식별자(UUID)가 있습니다.

 

BLE Services

 

디바이스 프로파일은 BLE 아키텍처의 최상위 레벨입니다. BLE 디바이스 프로파일은 하나 또는 여러 개의 BLE 서비스를 포함할 수 있습니다.

 

BLE Characteristics

 

BLE 서비스는 서비스에 필요한 실제 데이터를 담고 있는 특성을 가지고 있습니다. 모든 BLE 서비스는 하나 또는 여러 개의 BLE 특성을 가질 수 있습니다. 일반적인 BLE 특성의 예로는 BLE 서버에 연결된 센서에서 읽은 습도 또는 온도 측정값이 있습니다.

 

아래에는 BLE 클라이언트가 검색할 BLE 서버의 BLE 서비스 UUID와 BLE 특성 UUID를 정의했습니다. 제조사에서 제공하는 센서의 경우 해당 기기의 UUID가 제공됩니다. 하지만 이 예시에서는 자체 UUID를 생성해야 했습니다. 이를 위해 다음 무료 UUID 생성기를 사용했습니다: UUID Generator

 

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

 

아두이노 setup() 함수

 

이미 알고 계시겠지만, 아두이노 환경에서 ESP32를 시작하면 가장 먼저 실행되는 것이 setup() 함수입니다. 그리고 setup() 함수에서 가장 먼저 해야 할 일은 115200bps의 전송 속도를 사용하여 시리얼 모니터와의 시리얼 통신을 초기화하는 것입니다.

 

 Serial.begin(115200);

 

이제 init() 함수와 createServer() 함수를 사용하여 "PEA – BLE Server Test"라는 이름으로 새 BLE 서버 장치를 생성합니다.

 

BLEDevice::init("PEA - BLE Server Test");
  BLEServer *pServer = BLEDevice::createServer();

 

이전에 선언한 UUID를 사용하여 이 BLE 서버에 대한 BLE 서비스와 BLE 특성을 생성합니다. 또한 BLE 클라이언트가 BLE 서버의 특성 값에 접근할 수 있도록 특성에 읽기 및 쓰기 속성이 모두 설정되어 있는지 확인하십시오.

 

BLEService *pService = pServer->createService(SERVICE_UUID);
  BLECharacteristic *pCharacteristic = pService->createCharacteristic(

                                         CHARACTERISTIC_UUID,
                                         BLECharacteristic::PROPERTY_READ |
                                         BLECharacteristic::PROPERTY_WRITE

                                       );

 

BLE 특성 값을 "저희는 프로그래밍 전자 아카데미를 사랑합니다"로 초기화하고 서비스를 시작하세요. 이 값은 BLE 클라이언트가 저희 BLE 서버에 연결할 때 보게 되는 값입니다.

 

pCharacteristic->setValue("We love Programming Electronics Academy");
  pService->start();

 

마지막으로 BLE 서비스 광고를 시작하여 BLE 클라이언트가 BLE 서버를 찾고 연결하여 통신할 수 있도록 합니다.

 

BLEAdvertising *pAdvertising = BLEDevice::getAdvertising();

  pAdvertising->addServiceUUID(SERVICE_UUID);
  pAdvertising->setScanResponse(true);
  pAdvertising->setMinPreferred(0x06);  
  pAdvertising->setMinPreferred(0x12);
  BLEDevice::startAdvertising();

  Serial.println("Characteristic defined!");

 

아두이노의 loop() 함수

 

ESP32 BLE 서버의 모든 기능은 setup 함수에서 실행됩니다. 따라서 아두이노 메인 루프에서는 할 일이 전혀 없습니다.

 

아두이노 BLE 클라이언트 스케치

 

BLE 서버 스케치를 테스트하려면 먼저 BLE 클라이언트를 생성하고 실행해야 합니다!

 

다음 코드는 ESP32를 사용하여 BLE 클라이언트를 만드는 전체 예제 스케치입니다. 앞서 언급했듯이, 아래 코드에 대한 자세한 내용은 여기에서 확인할 수 있는 전체 강의를 참조하십시오.

 

ESP32를 컴퓨터에 연결했으면 전체 스케치를 장치에 업로드하세요. 스케치를 ESP32에 업로드한 후 ESP32의 리셋(RST) 버튼을 누르십시오.

 

//  BLE Client Example Sketch
//
//  Programming Electronics Academy
//

#include <BLEDevice.h>            // sets up BLE device constructs
// The remote service we wish to connect to.

static BLEUUID serviceUUID("4fafc201-1fb5-459e-8fcc-c5c9c331914b");
// The characteristic of the remote service we are interested in.

static BLEUUID charUUID("beb5483e-36e1-4688-b7f5-ea07361b26a8");
static boolean doConnect = false;
static boolean connected = false;
static boolean doScan = false;
static BLERemoteCharacteristic* pRemoteCharacteristic;
static BLEAdvertisedDevice* myDevice;
static void notifyCallback(

  BLERemoteCharacteristic* pBLERemoteCharacteristic,
  uint8_t* pData,
  size_t length,
  bool isNotify) {

    Serial.print("Notify callback for characteristic ");
    Serial.print(pBLERemoteCharacteristic->getUUID().toString().c_str());
    Serial.print(" of data length ");
    Serial.println(length);
    Serial.print("data: ");
    Serial.println((char*)pData);

}

class MyClientCallback : public BLEClientCallbacks {
  void onConnect(BLEClient* pclient) {

  }

  void onDisconnect(BLEClient* pclient) {
    connected = false;
    Serial.println("onDisconnect");

  }

};

bool connectToServer() {

    Serial.print("Forming a connection to ");
    Serial.println(myDevice->getAddress().toString().c_str());
    BLEClient*  pClient  = BLEDevice::createClient();
    Serial.println(" - Created client");

    pClient->setClientCallbacks(new MyClientCallback());
    // Connect to the remove BLE Server.

    pClient->connect(myDevice);
    Serial.println(" - Connected to server");

    // Obtain a reference to the service we are after in the remote BLE server.

    BLERemoteService* pRemoteService = pClient->getService(serviceUUID);
    if (pRemoteService == nullptr) {
      Serial.print("Failed to find our service UUID: ");
      Serial.println(serviceUUID.toString().c_str());
      pClient->disconnect();
      return false;

    }

    Serial.println(" - Found our service");

    // Obtain a reference to the characteristic in the service of the remote BLE server.

    pRemoteCharacteristic = pRemoteService->getCharacteristic(charUUID);
    if (pRemoteCharacteristic == nullptr) {

      Serial.print("Failed to find our characteristic UUID: ");
      Serial.println(charUUID.toString().c_str());
      pClient->disconnect();
      return false;

    }

    Serial.println(" - Found our characteristic");

    // Read the value of the characteristic.
    if(pRemoteCharacteristic->canRead()) {

      std::string value = pRemoteCharacteristic->readValue();
      Serial.print("The characteristic value was: ");
      Serial.println(value.c_str());
      Serial.println("");

    }

    if(pRemoteCharacteristic->canNotify())
      pRemoteCharacteristic->registerForNotify(notifyCallback);
    connected = true;

}

// Scan for BLE servers and find the first one that advertises the service we are looking for.
class MyAdvertisedDeviceCallbacks: public BLEAdvertisedDeviceCallbacks {

  // Called for each advertising BLE server.
  void onResult(BLEAdvertisedDevice advertisedDevice) {

    Serial.print("BLE Advertised Device found: ");
    Serial.println(advertisedDevice.toString().c_str());

    // We have found a device, let us now see if it contains the service we are looking for.
    if (advertisedDevice.haveServiceUUID() && advertisedDevice.isAdvertisingService(serviceUUID)) {

      BLEDevice::getScan()->stop();
      myDevice = new BLEAdvertisedDevice(advertisedDevice);
      doConnect = true;
      doScan = true;

    } // Found our server

  } // onResult

}; // MyAdvertisedDeviceCallbacks

void setup() {

  Serial.begin(115200);
  Serial.println("Starting Arduino BLE Client application...");

  BLEDevice::init("");
  // Retrieve a Scanner and set the callback we want to use to be informed when we
  // have detected a new device.  Specify that we want active scanning and start the
  // scan to run for 5 seconds.

  BLEScan* pBLEScan = BLEDevice::getScan();
  pBLEScan->setAdvertisedDeviceCallbacks(new MyAdvertisedDeviceCallbacks());
  pBLEScan->setInterval(1349);
  pBLEScan->setWindow(449);
  pBLEScan->setActiveScan(true);
  pBLEScan->start(5, false);

} // End of setup.

// This is the Arduino main loop function that runs repeatedly.

void loop() {

  // If the flag "doConnect" is true then we have scanned for and found the desired
  // BLE Server with which we wish to connect.  Now we connect to it.  Once we are 
  // connected we set the connected flag to be true.

  if (doConnect == true) {
    if (connectToServer()) {
      Serial.println("We are now connected to the BLE Server.");

    } else {

      Serial.println("We have failed to connect to the server; there is nothin more we will do.");

    }

    doConnect = false;

  }

  // If we are connected to a peer BLE Server, update the characteristic each time we are reached
  // with the current time since boot.

  if (connected) {
    String newValue = "Time since boot: " + String(millis()/1000);
    Serial.println("nSetting new characteristic value to "" + newValue + """);

    // Set the characteristic's value to be the array of bytes that is actually a string.
    pRemoteCharacteristic->writeValue(newValue.c_str(), newValue.length());

  }else if(doScan){

    BLEDevice::getScan()->start(0);  // this is just an example to re-start the scan after disconnect

  }

  delay(1000); // Delay a second between loops.

}

 

 

아두이노 ESP32 BLE 서버 스케치 테스트

 

ESP32 BLE 서버 스케치를 실행한 후, 별도의 ESP32에 ESP32 BLE 클라이언트 스케치를 업로드하십시오.

 

BLE 클라이언트 스케치를 ESP32에 업로드한 후 ESP32 장치의 리셋(RST) 버튼을 누르면 시리얼 모니터에 다음 내용이 표시됩니다.

 

 

ESP32 BLE 서버 - 시리얼 모니터

 

출력에서 볼 수 있듯이 ESP32 BLE 클라이언트는 변수 선언에서 정의한 서비스 UUID를 가진 "PEA – BLE Server Test"라는 이름의 ESP32 BLE 서버를 찾았습니다. ESP32 BLE 클라이언트는 이 서비스에 연결되어 "We love Programming Electronics Academy"라는 문자열 값을 포함하는 특성 UUID를 찾았습니다.

 

연결이 설정되면 ESP32 BLE 클라이언트는 아두이노 메인 루프() 함수에서 초당 한 번씩 특성 값을 반복적으로 업데이트합니다.

 

이게 전부입니다! ESP32 장치와 Arduino IDE를 사용하면 이렇게 간단하게 BLE 서버를 만들 수 있습니다!

 

여기서 다음 목적지는 어디인가요?

 

ESP32 장치를 사용하여 BLE 서버를 구축하면 ESP32 프로젝트에 완전히 새로운 가능성을 열어줄 수 있습니다. 이 기능은 특히 ESP32 주변의 센서(온도, 습도 등)와 데이터를 교환할 때 유용합니다.

 

연습 삼아 BLE 서비스와 BLE 특성에 직접 생성한 UUID를 사용하도록 코드를 수정하고 BLE 서버 이름을 업데이트해 보세요. BLE 클라이언트가 스캔하는 UUID도 업데이트해야 한다는 점을 잊지 마세요!

 

 

반응형

캐어랩 고객 지원

취업, 창업의 막막함, 외주 관리, 제품 부재!

당신의 고민은 무엇입니까? 현실과 동떨어진 교육, 실패만 반복하는 외주 계약, 아이디어는 있지만 구현할 기술이 없는 막막함.

우리는 알고 있습니다. 문제의 원인은 '명확한 학습, 실전 경험과 신뢰할 수 있는 기술력의 부재'에서 시작됩니다.

이제 고민을 멈추고, 캐어랩을 만나세요!

코딩(펌웨어), 전자부품과 디지털 회로설계, PCB 설계 제작, 고객(시장/수출) 발굴과 마케팅 전략으로 당신을 지원합니다.

제품 설계의 고수는 성공이 만든 게 아니라 실패가 만듭니다. 아이디어를 양산 가능한 제품으로!

귀사의 제품을 만드세요. 교육과 개발 실적으로 신뢰할 수 있는 파트너를 확보하세요.

지난 30년 여정, 캐어랩이 얻은 모든 것을 함께 나누고 싶습니다.

카카오 채널 추가하기

카톡 채팅방에서 무엇이든 물어보세요

당신의 성공을 위해 캐어랩과 함께 하세요.

캐어랩 온라인 채널 바로가기

캐어랩