본문으로 바로가기

[Android App] BLE GATT:: [4] characteristic read/write/ notify

category Android_app 2023. 6. 20. 11:00

: 이번에는  Characteristic 에 대한 read/write/notify 하는 방법에 대해 알아 보겠습니다.

   아시겠지만 블루투스 코드는 성능이 느리므로 UI 가 아닌 service class 에 메서드를 만들고

   호출하게 구현했습니다.  GATT callback에 대한 처리는 broadcast 로 메시지를 전송하고

   Activity 에서 이를 받아서 처리했습니다.   

 

▶ Characteristic read

   : Characteristic read는 BluetoothGatt 클래스의 readCharacteristic() 메서드를 이용합니다.

     다음처럼 선언이 되어 있습니다.

 

       Characteristic 속성 값에 아래처럼 READ 가 존재하는 특성에 대해서 체킹후 호출해야 합니다.

 

 

 → Characteristic Read 버튼 UI  코드  (Jetpack Compose) 작성하기

    : 아래코드는 Activity 안의 UI 코드 입니다.

    private var bluetoothLeService :BluetoothLeService?= null
    private var mBtNordicBtnCharacteristic : BluetoothGattCharacteristic? = null
    ~~ 중략 ~~
    
    Button(onClick = {
        bluetoothLeService?.readCharacteristic(mBtNordicBtnCharacteristic!!)
    }){
        Text("READ BTN ")
    }

 

 → BluetoothGatt::readCharacteristic() 메소드 호출코드를 포함한 메서드 만들기  

    : BluetoothLeService 클래스 안에 readCharacteristic() 메소드 선언 및 내용 작성 

// 1> BluetoothGatt::readCharacteristic() 사용할 메서드 만들기
class BluetoothLeService : Service() {
    private var mBtGatt :BluetoothGatt? = null
    fun readCharacteristic(characteristic: BluetoothGattCharacteristic){
        if( mBtGatt == null){
            Log.w(TAG,"BluetoothGatt is not initialized")
        }else {
            mBtGatt?.readCharacteristic(characteristic)
            Log.d(TAG, "readCharacteristic() --> ${characteristic.uuid}")
        }
    }
}

 

 → readCharacteristic() 의 결과를   리턴 받을 BluetoothGattCallback() 객체의

     onCharacteristicRead() 메서드 생성/구현 하기

// 2> BluetoothGattCallback :: onCharacteristicRead() 추가
    private val mGattCallback = object :BluetoothGattCallback() {
        override fun onCharacteristicRead( gatt: BluetoothGatt?,
                            characteristic: BluetoothGattCharacteristic?, status: Int   ) {
            if(status == BluetoothGatt.GATT_SUCCESS){
                Log.d(TAG,"GATT Callback --> onCharacteristicRead")
            }
        }
    }

 

 → 들어온데이타를 Activity 로 전달하기위한 Broadcast 코드 작성

     : BluetoothGattCallback() 객체안에서 boradcast 메시지를 전송 및 Activity 에서 BroadcastReceiver()를

       통한 데이타 파싱하기

// 3> Activity 로 읽은 데이타 전송하기
    3-0> broadcastUpdate 메서드 추가
    private fun broadcastUpdate(action:String, characteristic: BluetoothGattCharacteristic){
        val intent = Intent(action)

        // For all other profiles, writes the data formated in HEX.
        val data  = characteristic.value
        if(data != null && data.isNotEmpty()){
            val stringBuilder = StringBuilder(data.size)
            data.forEach {
                stringBuilder.append(String.format("%02X ", it))
            }
            intent.putExtra(EXTRA_DATA, stringBuilder.toString())
        }
        sendBroadcast(intent)
    }
    
    
    3-1> onCharacteristicRead()에 broadcastUpdate 메서드 호출하기
    override fun onCharacteristicRead( gatt: BluetoothGatt?,
                     characteristic: BluetoothGattCharacteristic?, status: Int   ) {
        if(status == BluetoothGatt.GATT_SUCCESS){
            broadcastUpdate(ACTION_DATA_AVAILABLE,characteristic)
        }
    }
    
    3-2> BroadcastReceiver 에 broadcastUpate() 에서 보낸 데이타 읽는 코드 추가
        private val mGattUpdateReceiver = object :BroadcastReceiver() {
        override fun onReceive(p0: Context?, p1: Intent?) {
            val action =p1!!.action
            when(action){
                BluetoothLeService.ACTION_DATA_AVAILABLE -> {
                    val data=p1.getStringExtra(BluetoothLeService.EXTRA_DATA)
                } 
            }
            ~~ 중략 ~~
        }

 

▶ Characteristic write

   : write 시 사용되는 메서드는 BluetoothGatt::writeCharacteristic() 함수로 다음처럼 사용합니다.

// 1> Service class에 writeCharacteristic() 함수 작성하기
class BluetoothLeService : Service() {
    private var mBtGatt :BluetoothGatt? = null
    ~~ 중략 ~~
    fun writeCharacteristic(charac:BluetoothGattCharacteristic, data:ByteArray)
    {
        if( mBtGatt == null){
            Log.w(TAG,"BluetoothGatt is not initialized")
        }else {
            charac.value = data
            mBtGatt?.writeCharacteristic(charac)
        }
    }
}

// 2> Activity 에서 함수 호출하기
    private var bluetoothLeService :BluetoothLeService?= null
    ~~ 중략 ~~
    val value = byteArrayOf(0x31,0x32,0x33)
    bluetoothLeService!!.writeCharacteristic(mBtNordicUartRxCharac!!,value)
    
    
// 3> BluetoothGattCallback 에 onCharacteristicWrite() 추가
private val mGattCallback = object :BluetoothGattCallback() {
    override fun onCharacteristicWrite( gatt: BluetoothGatt?,
        characteristic: BluetoothGattCharacteristic?,
        status: Int
    ) {
        if(status == BluetoothGatt.GATT_SUCCESS){
            Log.d(TAG,"GATT Callback --> onCharacteristicWrite OK")
        }
    }
}

  ==> Write or Write No Response  두가지 경우 모두 BluetoothGattCallback() 객체의

        onCharacteristicWrite() 메서드가 호출됩니다.

 

 

▶ Characteristic notify

  :  notify 는 read 처럼 호출 후 응답하는 방식이 아닌 peripheral 장치에서 직접 값을 넣어 보내줄 경우

    호출되는 방식입니다.

   BluetoothGatt::setCharacteristicNotification()을 사용해 특성에 대한 notify를 enable 한후

  BluetoothGattCallback() 의 onCharacteristicChanged() 메서드를 추가해 줍니다.

 

 

 → Nordic UART Service 찾기

     :   BluetoothGattCallback() 내부 메서드중 onServiceDiscovered 에서 작성해도 됩니다.

        저는 BroadcastReceiver() 사용함.

    private val mBtNordicUartServiceUuid = "6e400001-b5a3-f393-e0a9-e50e24dcca9e"
    private var mBtNordicUartService : BluetoothGattService? = null
    
    private val mGattUpdateReceiver = object :BroadcastReceiver() {
    override fun onReceive(p0: Context?, p1: Intent?) {
        val action =p1!!.action
        when(action){
            ~~ 중략 ~~
            BluetoothLeService.ACTION_GATT_SERVICE_DISCOVERED -> {
                mBtGattServices= bluetoothLeService!!.getSupportedGattServices()
                mBtGattServices!!.forEach {
                    if(it.uuid.toString() == mBtNordicUartServiceUuid){
                        mBtNordicUartService=it
                    }
                }
            }
             ~~ 중략 ~~
        }
        ~~ 중략 ~~
    }

 

 → NOTIFY 속성 characteristic 에 notify enable 하기

    fun setCharateristicNotification(characteristic: BluetoothGattCharacteristic, enabled: Boolean){
        if(btAdapter == null || mBtGatt == null){
            Log.w(TAG,"BluetoothAdapter not initialized")
        }
        mBtGatt?.setCharacteristicNotification(characteristic, enabled)
    }

CCCD 에 값 notification enable 쓰기

{
    ~~ 중략 ~~
    val uuid: UUID = UUID.fromString("00002902-0000-1000-8000-00805f9b34fb")
    val descriptor = characteristic.getDescriptor(uuid)
    descriptor.value= BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE
    mBtGatt!!.writeDescriptor(descriptor)
}

 

 

 

 

 → 데이타를 받기위한 BluetoothGattCallback() 객체 에 onCharacteristicChanged() 메서드 오버라이드하기

private val mGattCallback = object :BluetoothGattCallback() {
	~~ 중략 ~~
    override fun onCharacteristicChanged(   gatt: BluetoothGatt?,
        characteristic: BluetoothGattCharacteristic?
    ) {
        Log.d(TAG,"GATT Callback --> onCharacteristicChanged OK =>  ${String(characteristic!!.value)}")
    }
}

 

 → 이후 Activity 로 데이타 전송하는 과정은 read 와 동일합니다.

 

 

 

 

 

 

<이전 강좌>

https://leevisual.tistory.com/229

 

[Android App] BLE GATT:: [3] Characteristic /Descriptor 출력하기

:BLE GATT:: [2] 번 글 연속 강의 ==> [2]번 글 링크는 마지막에 넣었습니다. 서비스 리스트를 출력했으니 다음으로 서비스 아래의 특성(Chracteristic) 및 디스크립터를 출력해 보겠습니다. 목차 ▶ UUID 는

leevisual.tistory.com

 

 

 

 

그럼 오늘도 수고하세요.

반응형