[Android] 저전력 블루투스 정리하기

김방토·2025년 4월 30일
0

Android

목록 보기
8/10

📶 BLE 통신 흐름 (Central 역할 기준)

  1. 권한 요청 및 Bluetooth 활성화 확인
  2. BLE 장치 스캔 (Scan)
  3. 장치에 연결 (ConnectGatt)
  4. 서비스 검색 (Discover Services)
  5. 특정 특성(characteristic) 읽기/쓰기 또는 알림 구독

🛠️ 사전 설정 (권한 & Bluetooth 설정)

<!-- 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" /> <!-- Android 10 이하 -->
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" android:usesPermissionFlags="neverForLocation" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />

<!-- BLE 기능 선언 -->
<uses-feature android:name="android.hardware.bluetooth_le" android:required="true"/>

Android 12 (API 31) 이상부터는 BLUETOOTH_SCAN, BLUETOOTH_CONNECT 권한이 필요
/ + 런타임 권한 요청도 필수!

💡 기본 BLE 코드 흐름 (Central 입장)

BLE 스캔 시작하기

val bluetoothAdapter: BluetoothAdapter by lazy {
    val bluetoothManager = getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager
    bluetoothManager.adapter
}

val scanner: BluetoothLeScanner = bluetoothAdapter.bluetoothLeScanner
val scanCallback = object : ScanCallback() {
    override fun onScanResult(callbackType: Int, result: ScanResult) {
        val device = result.device
        Log.d("BLE", "디바이스 찾음: ${device.name} - ${device.address}")
    }
}

fun startScan() {
    val scanFilters = listOf<ScanFilter>() // 필요시 특정 서비스 UUID 필터 가능
    val settings = ScanSettings.Builder().build()
    scanner.startScan(scanFilters, settings, scanCallback)
}

디바이스 연결

var bluetoothGatt: BluetoothGatt? = null

fun connectToDevice(device: BluetoothDevice, context: Context) {
    bluetoothGatt = device.connectGatt(context, false, gattCallback)
}

Gatt 콜백 정의

private val gattCallback = object : BluetoothGattCallback() {
    override fun onConnectionStateChange(gatt: BluetoothGatt, status: Int, newState: Int) {
        if (newState == BluetoothProfile.STATE_CONNECTED) {
            Log.d("BLE", "연결 성공, 서비스 검색 시작")
            gatt.discoverServices()
        } else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
            Log.d("BLE", "연결 끊김")
        }
    }

    override fun onServicesDiscovered(gatt: BluetoothGatt, status: Int) {
        for (service in gatt.services) {
            Log.d("BLE", "서비스 발견: ${service.uuid}")
            for (characteristic in service.characteristics) {
                Log.d("BLE", "  특성: ${characteristic.uuid}")
            }
        }
    }

    override fun onCharacteristicRead(gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic, status: Int) {
        val value = characteristic.value
        Log.d("BLE", "특성 읽기 결과: ${value?.decodeToString()}")
    }

    override fun onCharacteristicChanged(gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic) {
        val value = characteristic.value
        Log.d("BLE", "특성 알림 수신: ${value?.decodeToString()}")
    }
}

특정 특성 읽기 / 쓰기 / 알림 설정

// 읽기
val characteristic = gatt.getService(SERVICE_UUID).getCharacteristic(CHAR_UUID)
gatt.readCharacteristic(characteristic)

// 쓰기
characteristic.value = "Hello".toByteArray()
gatt.writeCharacteristic(characteristic)

// 알림 등록
gatt.setCharacteristicNotification(characteristic, true)

🔗 BLE 통신 주요 객체와 역할

  • BluetoothManager 시스템의 전반적인 관리 담당, BluetoothAdapter를 얻을 때 사용
  • BluetoothAdapter 로컬 디바이스의 블루투스 어댑터. BLE 스캔 시작, 중지 등에 사용
  • BluetoothLeScanner BLE 장치를 검색(Scan)하는 데 사용
  • ScanCallback 스캔 중 BLE 장치가 발견되었을 때 호출
  • BluetoothDevice 검색된 장치 또는 MAC 주소로 생성된 장치 객체. 이 객체로 연결을 시작
  • BluetoothGatt BLE 장치와 GATT 프로토콜 기반으로 통신. 연결, 서비스 탐색, 특성 읽기/쓰기 등 수행
  • BluetoothGattCallback GATT 연결 상태, 서비스 검색 완료, 특성 변경 등 BLE 통신 이벤트 응답 처리
  • BluetoothGattService BLE 장치가 제공하는 기능 묶음. 하나 이상의 Characteristic을 포함
  • BluetoothGattCharacteristic 읽기/쓰기/알림이 가능한 단위. UUID를 통해 어떤 역할인지 구분
  • BluetoothGattDescriptor 알림 활성화 시 사용

👀 GATT?

GATT는 Generic Attribute Profile의 약자로,
Bluetooth Low Energy에서 데이터를 주고받는 방식을 정의한 프로토콜!
"BLE에서 데이터를 어떻게 주고받을지에 대한 통신 규칙"

profile
🍅 준비된 안드로이드 개발자 📱

0개의 댓글