본문 바로가기
Mobile : Android/Framework

[Android] 저전력 블루투스(Bluetooth LE) #1 - BLE 기본 개념

by 신숭이 2022. 6. 24.

[Android] 저전력 블루투스(Bluetooth LE) #1 - BLE 기본 개념

 

 

 

 

 

저전력 블루투스(Bluetooth Low Energy, LE) 기술은 앱과 연결되는 각종 장치들의 통신에서 소모되는 전력량을 줄이기 위해 탄생한 기술로, 많은 블루투스 기기들이 앱과 연결하여 데이터를 주고받을 때 활용된다. 블루투스 아키텍처는 전부다 서술하기엔 (물론 전부 다 공부하지도 못했지만) 양이 매우 많아, 저전력 통신 구현에 필요한 최소한의 필수 아키텍처 구조를 먼저 알아보고, 안드로이드(Kotlin)에서 실제 구현은 어떻게하는지는 다음 포스트에서 이어가겠다.

 

 

 

 

1. 안드로이드 블루투스 아키텍쳐

2. GAP와 GATT

3. 블루투스 시스템 API와 권한 처리

 

 

 

안드로이드 블루투스 아키텍처

 

 

우선 그림으로 보면 다음과 같다. Appication 개발 레벨에서 알아야 할 정보를 간략하고 짚고 넘어가겠다.

 

 

Application Framework

앱 개발자가 사용자가 블루투스 기기와 연동되는 작업을 수행할 화면과 컴포넌트(서비스, 브로드캐스트) 등을 설정하는 레이어로, android.bluetooth 패키지의 API를 활용하여 블루투스 기능을 사용할 수 있다. 

 

Bluetooth Process

Bluetooth Service는 시스템 서비스로 프레임워크 레이어에 각종 기능을 제공하고, Bluetooth Profile은 각종 블루투스 기기들과의 통신 프로토콜을 정의한다. 이 프로세스에서 JNI를 통해 스마트폰 칩셋의 블루투스 디바이스(하드웨어)와 정보를 교환한다.

 

Bluetooth Profile

Bluetooth Profile은 블루투스 디바이스 사이의 통신 규약을 말하는 것으로, 가장 대표적인 것이 GAP와 GATT 방식이다. GAP(Gerneric Access Profile)은 모든 블루투스 스캔이나 디바이스 간 연결에 필요한 처리를 정의하고 있기에 필수적인 프로토콜로, 개발 과정에서 자연히 수행하게 된다. GATT(Gerneric Attribute Profile)은 저전력 블루투스 통신을 위한 프로토콜로, 별도의 속성 프로토콜(ATT)을 사용하기 때문에 GATT/ATT이라고 표현하기도 한다. 

 

 

 

 

 

 

GAP와 GATT

 

1. GAP(Generic Access Profile)

이 프로파일은 개발자가 지정하거나 선택하는 프로토콜이 아니라, 모든 블루투스 또는 BLE 기기간 스캔 및 연결에 반드시 필요한 과정을 정의하고 있다. 이 프로토콜에서 모든 블루투스 기기들은 하나 이상의 역할(Role)이 부여되는데, 이를 기반으로  안드로이드 앱(스마트폰)심박 측정 장치(BLE 기기)를 찾아낼 수 있다.

 

여기서 역할(Role)은 스마트폰과 같이 구심점 역할을 할 수 있는 장치는 Central 역할(Master Device)을 맡게 되고, 심박 측정 장치와 같이 저전력으로 동작하고, 제한된 리소스를 가진 장치가 Peripheral 역할(Slave Device)을 맡게 된다. Peripheral 장치는 Central 장치가 자신을 찾아 연결할 수 있도록 advertising packet을 이용한다. (App 개발단에서는 몰라도 된다)

 

우리가 스마트폰에서 블루투스 기능을 ON 한 뒤, 목록에 각종 장치들이 뜨는 것이 보이는 게 바로 GAP 프로토콜에 정의된 내용이 동작하고 있고 이 프로토콜을 통해 Peripheral 기기의 각종 정보(Mac 주소, 이름, 기기 종류 등)를 알아낼 수 있다.  

 

 

 

 

2. GATT(Gerneric Attribute Profile)

BLE 장치가 서로 연결되고 데이터 통신 준비가 끝나면, 이제 GATT 방식으로 데이터를 주고받는다. GATT에서는 스마트폰이 GATT Client가 되고, 심박 측정 장치인 Periperal 기기가 GATT Server가 된다. 따라서 GATT Client인 스마트폰 앱이 기기에 요청을 보내고, 기기는 그 요청에 응답하는 방식으로 작동한다. (Client-Server 방식)

 

GATT Server (Profile)은 자신이 제공하는 서비스(Service, 기능 대분류)와 특성(Characteristic)이라 불리는 데이터를 다음과 같이 계층 구조로 보관하고 있다.

 

GATT Profile

여기서 Profile은 BLE 장치가 어떤 장치인지 나타내는 개념적인 구분이다. (여기서 말하는 Profile은 블루투스 Profile과 다른 개념이다) 일종의 블루투스 제품을 만들기 위한 일종의 명세로 쓰이기도 한다. 

 

GATT Service

보통 BLE 장치에는 다양한 센서가 탑재되는데, 각종 센서를 추상화한 것이 Service라고 보면 된다. 즉 아래의 예시에서 움직임 측정 센서가 움직임 측정 Service로 추상화되고, 각각의 센서 값은 특성 데이터(Characteristic)로 표현된다. 서비스는 서로를 구분하기 위한 고유의 128bit UUID 또는 미리 정의된 블루투스 표준 16bit UUID를 사용한다. 

출처 : https://www.hardcopyworld.com/?p=3086

* 예시

Service UUID :  195AE58A-437A-489B-B0CD-B7C9C394BAE4

표준 심박수 Service UUID : 180D

 

 

GATT Characteristic

실제 데이터를 담고 관리하는 것이 Characteristic으로, GATT Client(스마트폰)는 GATT Server(센서 기기)가 측정하여 변경할 수 있는 데이터인 Characteristic을 읽을 수 있다. 센서기기는 측정된 값으로 Characteristic 값을 바꿀 수 있고 스마트폰은 이를 읽는다. 이렇게 앱이 측정된 센서 값을 받아올 수 있는 것이다.

반대로 스마트폰(Client)이 센서(Server)의 Characteristic에 값을 쓸 수도 있다. 이렇게 되면 스마트폰이 센서에게 값을 전달하게 되는 것이다.

Characteristic도 각자를 구분하는 UUID가 지정되어있다.

 

Characteristic은 [Declaration, Value, Descriptor] 구성되어있다. 

  • Declaration : 일종의 헤더로서, [Properties, Value Handle, UUID]의 메타데이터로 구성된다. 여기서 Properties만 살펴보면, Characteristic의 특성을 결정하는 것인데, Write, Write without response, Read, Notify, Indicate 등의 특성을 부여할 수 있다. 참고로 GATT Client(스마트폰) 입장에서의 특성을 부여하는 것이다.  
    Notify는 Charateristic의 Value 가 변경이 발생할 때마다 GATT Client에 알려주는 Property이다. (구현할 때 사용할 것이니 잘 기억해두는 것이 좋다)
  • Value : 실제 데이터를 담는 영역이다. 
  • Descriptor : Notify, Indicate를 설정할 때 사용한다. Notify, Indication을 ON/OFF 하는 Descriptor 인 CCCD Descriptor라는 게 있다 정도만 알면 될 것 같다.

 

 

 

 

 

 

 

BLE 권한과 Android 블루투스 시스템 API

 

 

 

자세한 구현은 다음 포스트에 이어하겠다.

 

 

권한 설정

블루투스와 BLE는 다음의 권한을 필요로 한다. 타깃 버전에 따라 권한을 설정하면 된다. 이중 SCAN과 CONNECT 권한은 안드로이드 12 버전에서부터 런타임 권한으로 상향되었기에 반드시 사용자에게 권한 요청을 필요로 한다. 

  <uses-feature
        android:name="android.hardware.bluetooth"
        android:required="false" />
    <uses-feature
        android:name="android.hardware.bluetooth_le"
        android:required="false" />

    <uses-permission
        android:name="android.permission.BLUETOOTH"
        android:maxSdkVersion="30" />
    <uses-permission
        android:name="android.permission.BLUETOOTH_ADMIN"
        android:maxSdkVersion="30" /> 
    
    <!-- SDK 30 이하를 타겟팅 하는 앱 Bluetooth 권한 -->
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" /> 
    
    <!-- SDK 31 이상을 타겟팅 하는 앱 Bluetooth 권한 (Android 12+) -->
    <uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
    <uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />

 

한(Permission)에 대한 자세한 설명은 아래의 포스트에서 확인할 수 있다.

 

[Android] 권한(Permission) # 1 - 시스템 권한 (feat. Bluetooth)

[Android] 권한(Permission) # 1 - 시스템 권한 (feat. Bluetooth) 작성 시점 : 22.05.15 안드로이드 개발을 하다 보면, 특정 기능을 사용할 때 권한(Permission)이 필요한 경우가 있다. 특히 안드로이드 12 (API..

full-stack.tistory.com

 

 

 

자주 쓰이는 시스템 API 함수

1. 블루투스 매니저 : 블루투스 시스템 서비스로부터 API 함수를 이용하기 위해 참조한다.

// 블루투스 시스템 서비스 매니저 
val bluetoothManager = getSystemService(BLUETOOTH_SERVICE) as BluetoothManager

// 특정 프로파일(프로토콜)로 연결되어 있는 디바이스 정보 리스트 확인 : GATT - 연결된 BLE기기 목록
// 반환값은 BluetoothDevice 객체이다. (Mac 주소, 이름 등을 가진 객체)
val connectedDevices = bluetoothManager.getConnectedDevices(BluetoothProfile.GATT)

// 블루투스 어댑터 인스턴스 참조 획득
val bluetoothAdapter = bluetoothManager.adapter

참고로 연결과 페어링은 다른 말이다.

 

페어링(Pairing) = 본딩(Bonding) = 등록

 

블루투스 기기가 등록되었다는 것은 두 기기가 서로의 정보를 알고 있을 뿐, 연결된 상태는 아니다. 우리가 스마트폰 상단 탭에서 블루투스 버튼을 길게 눌러 나오는 등록된 기기 목록이 바로 그것이다.

 

 

 2. 블루투스 어댑터 : 스마트폰 내의 블루투스 디바이스 인스턴스를 참조한다.

// 블루투스 어댑터 참조 획득
mBluetoothAdapter = bluetoothManager.adapter

// 블루투스 기능 ON/OFF (상단 메뉴에서 누르는 것과 같음)
mBluetoothAdapter?.enable()	
mBluetoothAdapter?.disable()

// 특정 블루투스 디바이스(심박 측정 기기) 객체 참조
val bluetoothDevice = mBluetoothAdapter?.getRemoteDevice(address)

// BLE 스캐너 인스턴스 참조 획득
val bluetoothLeScanner = mBluetoothAdapter?.bluetoothLeScanner

// 등록된 디바이스 목록 확인 (연결된것은 아님)
val bondedDeviceList = mBluetoothAdapter?.bondedDevices

 

 

 

 

 

 

 

댓글