안드로이드 BLE 연결하기 feat.안드로이드 공식문서
이전 글에 이어서 안드로이드 공식문서를 토대로 BLE 연결하는 방식을 소개하겠습니다.
GATT server와 연결을 한 후, BLE 기기가 제공하는 Service를 찾고 데이터를 보내고 데이터를 받을 수 있습니다.
Discover services
다시 한 번 강조하지만, 여기서 Service와 안드로이드 기본 구성요소 Service를 혼동하시면 안됩니다.
BLE Service는 Ble기기가 전달하는 data 즉 Characterisic의 모음입니다.
혹시 이게 와닿지 않는다면, 그냥 Ble기기가 제공하는 서비스(역할)이라고 이해하시면 좋을 것 같습니다.
private final BluetoothGattCallback bluetoothGattCallback = new BluetoothGattCallback() {
@Override
public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
if (newState == BluetoothProfile.STATE_CONNECTED) {
// successfully connected to the GATT Server
Log.d(TAG, "onConnectionStateChange, coneccted to the GATT Server");
connectionState = STATE_CONNECTED;
broadcastUpdate(ACTION_GATT_CONNECTED);
if (ActivityCompat.checkSelfPermission(mContext, Manifest.permission.BLUETOOTH_CONNECT) != PackageManager.PERMISSION_GRANTED) {
return;
}
boolean result = gatt.discoverServices();
Log.d(TAG, "onConnectionStateChange, discoverServices: " + result);
} else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
// disconnected from the GATT Server
Log.d(TAG, "onConnectionStateChange, disconnected to the GATT Server");
connectionState = STATE_DISCONNECTED;
broadcastUpdate(ACTION_GATT_DISCONNECTED);
}
}
@Override
public void onServicesDiscovered(BluetoothGatt gatt, int status) {
if (ActivityCompat.checkSelfPermission(BluetoothLeService.this, Manifest.permission.BLUETOOTH_CONNECT) != PackageManager.PERMISSION_GRANTED) {
Log.d(TAG, "onServicesDiscovered: Ble Permission Denied");
return;
}
if (status == BluetoothGatt.GATT_SUCCESS) {
broadcastUpdate(ACTION_GATT_SERVICES_DISCOVERED);
List<BluetoothGattService> gattServices = gatt.getServices();
Log.d(TAG, "onServicesDiscovered, List of gattServices: " + gattServices.toString());
bluetoothGattWriteService = gattServices.get(3);
notifyCharacteristic = bluetoothGattWriteService.getCharacteristics().get(1);
notifyDescriptor = notifyCharacteristic.getDescriptor(Constants.READ_DESCRIPTOR_UUID);
notifyDescriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
boolean notificationDescriptor = gatt.writeDescriptor(notifyDescriptor);
Log.d(TAG, "onServicesDiscovered, notifyCharaceristic UUID: " + notifyCharacteristic.getUuid().toString());
if(notifyCharacteristic != null) {
boolean notificationResult = gatt.setCharacteristicNotification(notifyCharacteristic, true);
Log.d(TAG, "notificationResult: " + notificationResult);
}
if(notifyDescriptor != null){
Log.d(TAG, "notify Descriptor: " + notificationDescriptor);
}
for (BluetoothGattService service : gattServices) {
Log.d(TAG, "onServicesDiscovered, Service UUID: " + service.getUuid().toString());
List<BluetoothGattCharacteristic> characteristics = service.getCharacteristics();
Log.d(TAG, "onServicesDiscovered, characteristics: " + characteristics);
for (BluetoothGattCharacteristic characteristic : characteristics) {
if (hasProperty(characteristic, BluetoothGattCharacteristic.PROPERTY_READ)) {
//bjs: 여기서 ble 데이터 읽는다
Log.d(TAG, "onServicesDiscovered, characteristic: " + characteristic);
boolean result = gatt.readCharacteristic(characteristic);
Log.d(TAG, "onServicesDiscovered, read result: " + result);
}
if (hasProperty(characteristic, PROPERTY_WRITE)) {
boolean writeResult = gatt.writeCharacteristic(characteristic);
Log.d(TAG, "onServicesDiscovered, writeResult: " + writeResult);
}
}
}
} else {
Log.w(TAG, "onServicesDiscovered received: " + status);
}
}
@Override
public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
super.onCharacteristicRead(gatt, characteristic, status);
Log.d(TAG, "onCharacteristicRead: " + "gatt = " + gatt + "status = " + status);
byte[] value = characteristic.getValue();
String readData = byteArrayToHexaString(value);
Log.d(TAG, "onCharacteristicRead, readData: " + readData);
}
@Override
public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
super.onCharacteristicWrite(gatt, characteristic, status);
if(status == BluetoothGatt.GATT_SUCCESS) {
Log.d(TAG, "OnCharacteristicWrite: Write Success");
byte[] writeData = characteristic.getValue();
if(writeData != null) {
//String writeMessage = writeData.toString();
Log.d(TAG, "writeData: " + byteArrayToHexaString(writeData));
Intent sendIntent = new Intent(Constants.SEND);
sendIntent.putExtra(Constants.SEND_FLAG, Constants.RECEIVER_WRITE);
sendIntent.putExtra(Constants.SEND_MSG, new String(writeData));
sendIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
sendBroadcast(sendIntent);
}
}
}
@Override
public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
super.onCharacteristicChanged(gatt, characteristic);
Log.d(TAG, "onCharacteristicChanged: notify");
byte[] notifyData = characteristic.getValue();
if(notifyData != null) {
Log.d(TAG, "Notify Value: " + byteArrayToHexaString(characteristic.getValue()));
Intent sendIntent = new Intent(Constants.SEND);
sendIntent.putExtra(Constants.SEND_FLAG, Constants.RECEIVER_READ);
sendIntent.putExtra(Constants.SEND_MSG, new String(notifyData));
sendIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
sendBroadcast(sendIntent);
}
}
};
Ble를 통해 데이터를 주고받기 전에 서비스를 찾는 과정이 필요합니다.
discoverServices() 메서드를 실행하면 onServicesDiscovered 콜백이 호출됩니다.
저 같은 경우에는 BluetoothGattCallback에서 GATT Server와 연결이 이루어진 후
boolean result = gatt.discoverServices();
Log.d(TAG, "onConnectionStateChange, discoverServices: " + result);
를 통해서 서비스를 찾았습니다.
여기서 내가 사용할 서비스가 무엇인지 찾아야합니다.
이 과정에서 외부앱을 사용하면 굉장히 편리하다.
바로 nRF Connect 앱이다.
이 앱을 간단히 설명하자면 기기와 ble연결을 하여 데이터를 주고받는 통신을 테스트할 수 있는 앱이다.
기기를 이 앱과 연결하는 것은 UI상 직관적이므로 생략하겠다.
연결 이후에 화면을 보자.
데이터는 보통 Byte배열로 주고받는다.
@Override
public void onServicesDiscovered(BluetoothGatt gatt, int status) {
if (ActivityCompat.checkSelfPermission(BluetoothLeService.this, Manifest.permission.BLUETOOTH_CONNECT) != PackageManager.PERMISSION_GRANTED) {
Log.d(TAG, "onServicesDiscovered: Ble Permission Denied");
return;
}
if (status == BluetoothGatt.GATT_SUCCESS) {
broadcastUpdate(ACTION_GATT_SERVICES_DISCOVERED);
List<BluetoothGattService> gattServices = gatt.getServices();
Log.d(TAG, "onServicesDiscovered, List of gattServices: " + gattServices.toString());
bluetoothGattWriteService = gattServices.get(3);
notifyCharacteristic = bluetoothGattWriteService.getCharacteristics().get(1);
notifyDescriptor = notifyCharacteristic.getDescriptor(Constants.READ_DESCRIPTOR_UUID);
notifyDescriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
boolean notificationDescriptor = gatt.writeDescriptor(notifyDescriptor);
Log.d(TAG, "onServicesDiscovered, notifyCharaceristic UUID: " + notifyCharacteristic.getUuid().toString());
if(notifyCharacteristic != null) {
boolean notificationResult = gatt.setCharacteristicNotification(notifyCharacteristic, true);
Log.d(TAG, "notificationResult: " + notificationResult);
}
if(notifyDescriptor != null){
Log.d(TAG, "notify Descriptor: " + notificationDescriptor);
}
for (BluetoothGattService service : gattServices) {
Log.d(TAG, "onServicesDiscovered, Service UUID: " + service.getUuid().toString());
List<BluetoothGattCharacteristic> characteristics = service.getCharacteristics();
Log.d(TAG, "onServicesDiscovered, characteristics: " + characteristics);
for (BluetoothGattCharacteristic characteristic : characteristics) {
if (hasProperty(characteristic, BluetoothGattCharacteristic.PROPERTY_READ)) {
Log.d(TAG, "onServicesDiscovered, characteristic: " + characteristic);
boolean result = gatt.readCharacteristic(characteristic);
Log.d(TAG, "onServicesDiscovered, read result: " + result);
}
if (hasProperty(characteristic, PROPERTY_WRITE)) {
boolean writeResult = gatt.writeCharacteristic(characteristic);
Log.d(TAG, "onServicesDiscovered, writeResult: " + writeResult);
}
}
}
} else {
Log.w(TAG, "onServicesDiscovered received: " + status);
}
}
//what is property of Characteristic
public boolean hasProperty(BluetoothGattCharacteristic characteristic, int property) {
int prop = characteristic.getProperties() & property;
return prop == property;
}
bluetoothGattWriteService = gattServices.get(3);
notifyCharacteristic = bluetoothGattWriteService.getCharacteristics().get(1);
notifyDescriptor = notifyCharacteristic.getDescriptor(Constants.READ_DESCRIPTOR_UUID);
notifyDescriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
boolean notificationDescriptor = gatt.writeDescriptor(notifyDescriptor);
Log.d(TAG, "onServicesDiscovered, notifyCharaceristic UUID: " + notifyCharacteristic.getUuid().toString());
if(notifyCharacteristic != null) {
boolean notificationResult = gatt.setCharacteristicNotification(notifyCharacteristic, true);
Log.d(TAG, "notificationResult: " + notificationResult);
}
private boolean writeByBle(byte[] msg) {
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.BLUETOOTH_CONNECT) != PackageManager.PERMISSION_GRANTED) {
return false;
}
if (bluetoothGatt == null || bleAdapter == null) {
Log.d(TAG, "writeByBle: BluetoothAdapter and bluetoothGatt not initialized");
return false;
}
if(bluetoothGattWriteService == null){
Log.d(TAG, "writeByBle: bluetoothGattService is null!!");
}
BluetoothGattCharacteristic characteristic = bluetoothGattWriteService.getCharacteristic(Constants.WRITE_CHAR_UUID);
characteristic.setValue(msg);
return bluetoothGatt.writeCharacteristic(characteristic);
}
@Override
public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
super.onCharacteristicChanged(gatt, characteristic);
Log.d(TAG, "onCharacteristicChanged: notify");
byte[] notifyData = characteristic.getValue();
if(notifyData != null) {
Log.d(TAG, "Notify Value: " + byteArrayToHexaString(characteristic.getValue()));
Intent sendIntent = new Intent(Constants.SEND);
sendIntent.putExtra(Constants.SEND_FLAG, Constants.RECEIVER_READ);
sendIntent.putExtra(Constants.SEND_MSG, new String(notifyData));
sendIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
sendBroadcast(sendIntent);
}
}
characteristic.getValue를 통해 받은 데이터를 저장하구요.
이 데이터를 브로드캐스트를 통해 액티비티에 전달해주었습니다.
byteArrayToHexaString 함수는 구글링하다 발견한 함수인데 함수이름처럼 byteArray를 16진수 String으로 만들어주는 함수입니다.
public static String byteArrayToHexaString(byte[] bytes) {
StringBuilder builder = new StringBuilder();
for (byte data : bytes) {
builder.append(String.format("%02X ", data));
}
return builder.toString();
}
지금까지 ble연결을 통해서 데이터를 읽고 쓰는 것을 해보았습니다.
저도 처음 해보는 거라 여러 시행착오가 있었기 때문에 이 글을 읽는 개발자분들은 저와같은 시행착오를 줄이셨으면 좋겠네요.
긴 글 읽으시느라 수고 많으셨습니다.