안녕하세요 이번에는 BLE 통신예제를 해보도록 할게요.
우선 메니페스트에 권한을 먼저 넣어주겠습니다.
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.BLUETOOTH_SCAN"
android:usesPermissionFlags="neverForLocation" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-feature android:name="android.hardware.bluetooth_le" android:required="true" />
그리고 권한 체크도 꼭 해주세요.
permissions = arrayOf(
Manifest.permission.BLUETOOTH,
Manifest.permission.BLUETOOTH_ADMIN,
Manifest.permission.BLUETOOTH_SCAN,
Manifest.permission.BLUETOOTH_CONNECT,
Manifest.permission.BLUETOOTH_ADVERTISE,
Manifest.permission.ACCESS_FINE_LOCATION,
Manifest.permission.ACCESS_COARSE_LOCATION,
Manifest.permission.POST_NOTIFICATIONS
)
퍼미션 리스트입니다.
이중에는 위험권한이 아닌것도 있는데 저는 귀찮아서 그냥 다 때려박았네용 ㅎㅎ
그리고 이제 주변 BLE 장비를 스캔합니다.
if (!packageManager.hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) {
Toast.makeText(this@BlueToothListActivity,"BLE 미지원",Toast.LENGTH_SHORT).show()
Log.d(TAG,"BLE 미지원")
}
해당 조건문을 통하여 기기가 BLE를 지원하는지 여부를 체크 할 수 있습니다.
그다음에 스캔을 진행 하기 전에 앞서 스캔 결과값을 받아올 콜백을 만들어둘게요.
val scanCallback = object : ScanCallback() {
override fun onScanResult(callbackType: Int, result: ScanResult?) {
if (ActivityCompat.checkSelfPermission(context, Manifest.permission.BLUETOOTH_CONNECT) != PackageManager.PERMISSION_GRANTED) { return }
val device: BluetoothDevice? = result?.device
val deviceName: String = device?.name ?: "not device name"
val deviceAddress: String = device?.address ?: "not address"
val rssi: Int = result?.rssi ?: 0
}
override fun onScanFailed(errorCode: Int) {
// 스캔 실패 처리
}
}
여기서 각종 디바이스 정보를 얻을 수 있습니다.
리스트뷰나 리사이클러뷰에 자유롭게 뿌려보시는 것도 추천합니다.
하지만 결정적으로 디바이스에 BLE 통신을 위한 정보는
device 변수입니다.
자 이제 스캔을 시작해볼게요.
private val bluetoothManager: BluetoothManager by lazy {
getSystemService(BluetoothManager::class.java)
}
private val bluetoothAdapter: BluetoothAdapter? by lazy {
bluetoothManager.adapter
}
lateinit var bluetoothLeScanner : BluetoothLeScanner
bluetoothLeScanner = bluetoothAdapter!!.bluetoothLeScanner
val scanSettings: ScanSettings = ScanSettings.Builder()
.setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY)
.build()
bluetoothLeScanner?.startScan(null, scanSettings, scanCallback)
override fun onDestroy() {
super.onDestroy()
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.BLUETOOTH_SCAN) != PackageManager.PERMISSION_GRANTED) { }
bluetoothLeScanner?.stopScan(scanCallback)
}
해당 구문들을 통하여 스캔을 시작하고 위에서 미리 작성해둔 콜백에서 정보를 받을 수 있습니다.
엑티비티가 종료될때 스캔도 같이 종료해줍니다.
이제 연결을 시도해보겠습니다.
lateinit var mGatt: BluetoothGatt
fun connect(){
if (ActivityCompat.checkSelfPermission(this@MainActivity, Manifest.permission.BLUETOOTH_CONNECT) != PackageManager.PERMISSION_GRANTED) { }
mGatt = Global.bluetoothDevice?.connectGatt(this@MainActivity, false, gattCallback)!!
}
fun disConnect(){
if (ActivityCompat.checkSelfPermission(this@MainActivity, Manifest.permission.BLUETOOTH_CONNECT) != PackageManager.PERMISSION_GRANTED) { }
mGatt?.disconnect()
}
우선 블루투스 관련 유틸 클래스 하나 만들어주겠습니다.
UUID는 해당 모듈에 맞는걸로 재작성 해주셔야 할 수도 있습니다.
class BluetoothUtils {
companion object {
val WRITE_UUID = "0000fff2-0000-1000-8000-00805f9b34fb"
val NOTIFY_UUID = "0000fff1-0000-1000-8000-00805f9b34fb"
val NA_UUID = "0000fff0-0000-1000-8000-00805f9b34fb"
/**
* Find characteristics of BLE
* @param gatt gatt instance
* @return list of found gatt characteristics
*/
fun findBLECharacteristics(gatt: BluetoothGatt): List<BluetoothGattCharacteristic> {
val matchingCharacteristics: MutableList<BluetoothGattCharacteristic> = ArrayList()
val serviceList = gatt.services
val service = findGattService(serviceList) ?: return matchingCharacteristics
val characteristicList = service.characteristics
for (characteristic in characteristicList) {
if (isMatchingCharacteristic(characteristic)) {
matchingCharacteristics.add(characteristic)
}
}
return matchingCharacteristics
}
/**
* Find command characteristic of the peripheral device
* @param gatt gatt instance
* @return found characteristic
*/
fun findCommandCharacteristic(gatt: BluetoothGatt): BluetoothGattCharacteristic? {
return findCharacteristic(gatt, WRITE_UUID)
}
/**
* Find response characteristic of the peripheral device
* @param gatt gatt instance
* @return found characteristic
*/
fun findResponseCharacteristic(gatt: BluetoothGatt): BluetoothGattCharacteristic? {
return findCharacteristic(gatt, NOTIFY_UUID)
}
/**
* Find the given uuid characteristic
* @param gatt gatt instance
* @param uuidString uuid to query as string
*/
private fun findCharacteristic(gatt: BluetoothGatt, uuidString: String): BluetoothGattCharacteristic? {
val serviceList = gatt.services
val service = findGattService(serviceList) ?: return null
val characteristicList = service.characteristics
for (characteristic in characteristicList) {
if (matchCharacteristic(characteristic, uuidString)) {
return characteristic
}
}
return null
}
/**
* Match the given characteristic and a uuid string
* @param characteristic one of found characteristic provided by the server
* @param uuidString uuid as string to match
* @return true if matched
*/
private fun matchCharacteristic(characteristic: BluetoothGattCharacteristic?, uuidString: String): Boolean {
if (characteristic == null) {
return false
}
val uuid: UUID = characteristic.uuid
return matchUUIDs(uuid.toString(), uuidString)
}
/**
* Find Gatt service that matches with the server's service
* @param serviceList list of services
* @return matched service if found
*/
private fun findGattService(serviceList: List<BluetoothGattService>): BluetoothGattService? {
for (service in serviceList) {
val serviceUuidString = service.uuid.toString()
if (matchServiceUUIDString(serviceUuidString)) {
return service
}
}
return null
}
/**
* Try to match the given uuid with the service uuid
* @param serviceUuidString service UUID as string
* @return true if service uuid is matched
*/
private fun matchServiceUUIDString(serviceUuidString: String): Boolean {
return matchUUIDs(serviceUuidString, NA_UUID)
}
/**
* Check if there is any matching characteristic
* @param characteristic query characteristic
*/
private fun isMatchingCharacteristic(characteristic: BluetoothGattCharacteristic?): Boolean {
if (characteristic == null) {
return false
}
val uuid: UUID = characteristic.uuid
return matchCharacteristicUUID(uuid.toString())
}
/**
* Query the given uuid as string to the provided characteristics by the server
* @param characteristicUuidString query uuid as string
* @return true if the matched is found
*/
private fun matchCharacteristicUUID(characteristicUuidString: String): Boolean {
return matchUUIDs(
characteristicUuidString,
WRITE_UUID,
NOTIFY_UUID
)
}
/**
* Try to match a uuid with the given set of uuid
* @param uuidString uuid to query
* @param matches a set of uuid
* @return true if matched
*/
private fun matchUUIDs(uuidString: String, vararg matches: String): Boolean {
for (match in matches) {
if (uuidString.equals(match, ignoreCase = true)) {
return true
}
}
return false
}
}
}
여기서도 콜백을 만들어줘야해요.
var readMsg = ""
val gattCallback = object : BluetoothGattCallback() {
override fun onConnectionStateChange(gatt: BluetoothGatt?, status: Int, newState: Int) {
if (newState == BluetoothProfile.STATE_CONNECTED) {
Log.d(TAG,"연결성공")
if (ActivityCompat.checkSelfPermission(this@MainActivity, Manifest.permission.BLUETOOTH_CONNECT) != PackageManager.PERMISSION_GRANTED) { }
mGatt?.discoverServices()
}
else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
Log.d(TAG,"연결해제")
}
}
override fun onServicesDiscovered(gatt: BluetoothGatt, status: Int) {
when(status){
BluetoothGatt.GATT_SUCCESS -> {
Log.d(TAG,"블루투스 셋팅완료")
if (ActivityCompat.checkSelfPermission(this@MainActivity, Manifest.permission.BLUETOOTH_CONNECT) != PackageManager.PERMISSION_GRANTED) { }
val respCharacteristic = gatt?.let {
findResponseCharacteristic(it)
}
if( respCharacteristic == null ) {
Log.e(TAG, "블루투스 커맨드를 찾지 못하였습니다.")
return
}
gatt.setCharacteristicNotification(respCharacteristic, true)
val descriptor:BluetoothGattDescriptor = respCharacteristic.getDescriptor(UUID.fromString("00002902-0000-1000-8000-00805f9b34fb"))
descriptor.value = BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE
gatt.writeDescriptor(descriptor)
}
else -> {
Log.e(TAG,"블루투스 셋팅실패")
}
}
}
override fun onCharacteristicWrite(gatt: BluetoothGatt?, characteristic: BluetoothGattCharacteristic?, status: Int) {
super.onCharacteristicWrite(gatt, characteristic, status)
when(status){
BluetoothGatt.GATT_SUCCESS -> {
Log.d(TAG,"데이터 보내기 성공")
}
else -> {
Log.d(TAG,"데이터 보내기 실패")
}
}
}
//안드로이드 13이상 호출
override fun onCharacteristicChanged(gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic, value: ByteArray) {
super.onCharacteristicChanged(gatt, characteristic, value)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU){
Log.d(TAG,"블루투스 수신성공")
readMsg = String(value)
}
}
//안드로이드 12까지 호출
override fun onCharacteristicChanged(gatt: BluetoothGatt?, characteristic: BluetoothGattCharacteristic?) {
super.onCharacteristicChanged(gatt, characteristic)
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.S) {
Log.d(TAG,"블루투스 수신성공")
readMsg = characteristic?.getStringValue(0).toString()
}
}
}
onServicesDiscovered 함수는 현재 사용 가능한 uuid를 검색하여 등록해주는 역할을 해줍니다.
이제 데이터 보내는 예제를 보도록 하겠습니다.
val SERVICE = "0000fff0-0000-1000-8000-00805f9b34fb"
val READ_UUID = "0000fff2-0000-1000-8000-00805f9b34fb"
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.BLUETOOTH_CONNECT) != PackageManager.PERMISSION_GRANTED) { }
val service = mGatt.getService(UUID.fromString(SERVICE))
val characteristic = service.getCharacteristic(UUID.fromString(READ_UUID))
val command = "testCommand"
writeByte = command.toByteArray()
characteristic.setValue(command.toByteArray())
mGatt.writeCharacteristic(characteristic)
이렇게 데이터를 전송 할 수 있습니다.
이렇게하면 데이터 보내고 받기가 완료됩니다.
데이터 전송 시
onCharacteristicWrite
이녀석이 호출되구요.
데이터를 받을때는
onCharacteristicChanged
이녀석이 호출됩니다.
안드로이드 버전별로 호출되는 녀석이 상이하니 주의하세요 (주석참고)
엑티비티가 종료될때
override fun onDestroy() {
super.onDestroy()
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.BLUETOOTH_CONNECT) != PackageManager.PERMISSION_GRANTED) { }
mGatt?.disconnect()
}
연결도 종료해줍니다.
이상입니다.
'Android' 카테고리의 다른 글
biometric를 활용한 안드로이드 생체인증 (0) | 2024.02.23 |
---|---|
안드로이드 14 java.lang.SecurityException: Writable dex file '/data/data/packageName/code_cache/.overlay/base.apk/classes3.dex' is not allowed. (0) | 2024.02.07 |
안드로이드 글라이드로 이미지 URL 로드 시 변경 안될때 (0) | 2023.04.18 |
안드로이드 뷰페이져 인디케이터 사용해보기 (0) | 2023.03.29 |
안드로이드 targetSdk 33 onbackpressed deprecated (0) | 2023.03.14 |
댓글