Development record of developer who study hard everyday.

레이블이 안드로이드blescan인 게시물을 표시합니다. 모든 게시물 표시
레이블이 안드로이드blescan인 게시물을 표시합니다. 모든 게시물 표시
, , , ,

안드로이드 BLE 개념, 스캔, 연결 fea.안드로이드 공식 문서

안드로이드 Ble 스캔 및 연결하기 


회사에서 ble연결을 활용한 앱을 만들일이 생겼다.

원래는 블루투스 기기를 활용하여 앱과 연결한 후 땅의 깊이나 기울기를 측정하는 서비스였다.

그런데 기기를 업데이트하면서 이젠 블루투스 연결 뿐만 아니라 ble연결을 활용하여 기기와 앱을 연결을 해야한다.

그리고 데이터를 주고받으며 측정까지하는 업무였다.


우선, 안드로이드 공식문서를 여러번 읽어보았다.

한글로도 읽고 영어로된 원서도 읽었다.

처음엔 무슨 말인지 몰랐는데 2-3번 읽다보니 적응이 되었다.

지금부터 설명할 내용은 안드로이드 공식문서 번역 + 회사 프로젝트에 사용한 코드 + BLE 관련 구글링 내용을 적절히 섞어서 녹아내려고 한다.

기본 용어와 개념

Generic Attribute Profile (GATT)
GATT는 데이터 통신을 위한 서버라고 생각하면된다.

Profiles
음... 좀 애매할 수 있는데 프로파일이란 블루투스 연결에 사용되는 여러 객체의 대리자라고 생각하면 이해하기 쉽지않을까한다.

Attribute Protocol (ATT)
BLE 작업을 하면서 특별히 신경써야할 개념은 아니였다.
공식문서를 그대로 번역하자면 GATT가 ATT위에 만들어졌다고한다.

Characteristic
중요한 개념이다.
우리가 BLE를 통해 데이터를 주고받을 때 실질적으로 값을 갖고있는 녀석이다.
Characteristic은 또 여러개의 Descriptor를 가지고 있다.

Descriptor
Characteristic의 값을 가지고 있는 녀석이다.

Service
매우 중요한 개념이다.
BLE 디바이스가 제공하는 역할이라고 생각하면된다.
서비스는 여러개의 Characteristic으로 구성되어있다고 생각하면 된다.

Central vs Peripheral
BLE 연결을 성공적으로 하기 위해서는 휴대폰과 BLE기기 중 각각 Central과 Peripheral 역할을 맡아줘야한다.
Central은 스캔을 하여 BLE 기기를 찾고 Peripheral(BLE 기기)은 advertise를 하여 Central에게 자신이 BLE 연결 가능한 상태임을 알린다.

GATT server vs GATT client
GATT는 위에서 언급한대로 데이터를 주고받을 때 사용하는 개념이다.
데이터를 주고받기 위해서는 휴대폰과 BLE기기 중 각각 GATT server와 GATT client역할을 맡아줘야한다.

블루투스 권한


👆위 링크를 클릭해서 글을 읽어보자.
Android 12부터 블루투스 권한에 변화가 생겼다.
그리 어렵지 않으니 찬찬히 읽어보고 따라하면 문제가 없을 것이다.

BLE 기기 찾기(스캔)

간단히 말하자면, startScan 메소드로 스캔을 시작하고 ScanCallback을 구현하여 scan 결과값을 처리하면된다.

스캔은 배터리 소모가 많은 작업이라서 
1. 기기를 찾으면 바로 스캔을 종료한다
2. 스캔을 할 때는 시간을 꼭 정해준다.
이 두가지를 꼭 지켜주라고 공식문서에 나와있다.

private BluetoothLeScanner bleScanner;
private boolean scanning;
// Stops scanning after 10 seconds.
private static final long SCAN_PERIOD = 10000;
private Handler handler = new BTHandler();
public BluetoothDevice foundDevice;
private void scanLeDevice() {
if (!scanning) {
// Stops scanning after a predefined scan period.
handler.postDelayed(new Runnable() {
@Override
public void run() {
scanning = false;
if (checkBlePermission(blePermissions))
bleScanner.stopScan(leScanCallback);
}
}, SCAN_PERIOD);

scanning = true;
bleScanner.startScan(leScanCallback);
} else {
scanning = false;
bleScanner.stopScan(leScanCallback);
}
}
private final ScanCallback leScanCallback =
new ScanCallback() {
@Override
public void onScanResult(int callbackType, ScanResult result) {
super.onScanResult(callbackType, result);
/*leDeviceListAdapter.addDevice(result.getDevice());
leDeviceListAdapter.notifyDataSetChanged();*/
if (result != null && checkBlePermission(blePermissions)) {
BluetoothDevice bleDevice = result.getDevice();
Log.d(TAG, "onScanResult: " + bleDevice.getName() + ", " + bleDevice.getAddress());
//scanSet.add(new Pair(bleDevice.getName(), bleDevice.getAddress()));
if (bleDevice.getName() != null && bleDevice.getName().contains("ACE"))
foundDevice = bleDevice;
//scanSet.add(bleDevice);
}
//Log.d(TAG, "onScanResult, scanSet's size : " + scanSet.size() + "scanSet : " + scanSet.toString());

}
};
protected Boolean checkBlePermission(String[] permissions) {
for (String permission : permissions) {
if (checkSelfPermission(permission) == PackageManager.PERMISSION_DENIED) return false;
}
return true;
}
public static String[] blePermissions = new String[] {
Manifest.permission.BLUETOOTH_CONNECT,
Manifest.permission.BLUETOOTH_SCAN,
Manifest.permission.BLUETOOTH_ADVERTISE,
};

안드로이드 공식문서를 토대로 그냥 거의 그대로 베낀 수준이다.
크게 어려운 것은 없고, 회사 프로젝트가 자바로 되어있어서 나도 자바로 코드를 짰다.

ScanCallback을 구현할 때 스캔한 장비의 이름을 활용하여 내가 원하는 장비를 찾아서 foundDevice 변수에 저장해두었다.

기기와 BLE연결을 하기위해서는 디바이스의 MAC주소가 필요하다.
그래서 따로 저장해두었다.

GATT server와 연결하기

GATT 서버와 연결한다고 생각하면 되게 어렵게 느껴진다.
근데, 간단히 말하면 기기와 BLE연결을 한다는 것이 기기의 GATT 서버와 연결한다는 것과 같은 말이라고 생각하면 쉽다.
bluetoothGatt = device.connectGatt(this, false, bluetoothGattCallback);
위 코드가 디바이스의 GATT server와 연결하는 코드이다.
연결이 성공적으로 완료되면 BluetoothGatt 객체를 넘겨준다.
이 과정에서 GATT server는 Ble디바이스가 되고 Android App이 GATT client가 된다.

GATT server와 연결결과는 BluetoothGattCallback을 통해서 넘어온다.

서비스 시작하기


BLE 기기와 신호를 주고받기위해서 서비스 객체가 필요하다.

public class BluetoothLeService extends Service {
   
    private Binder binder = new LocalBinder();
    @Override     @Nullable
    public IBinder onBind(Intent intent) {
    return binder;
    }

    public class LocalBinder extends Binder {
    public BluetoothLeService getService() {
    return BluetoothLeService.this;
    //return getInstance();
    }
    }
}

나 같은 경우에는 처음부터 코드를 짠 것이 아니라 예전 사람이 회사에서 만든 코드를 수정하는 경우였다.
근데, 그 분이 Application 객체에다가 서비스를 시작했다;;
처음엔 이해가 안되어서 나 같은 경우에는 BaseActivity에다가 서비스를 시작했었는데 문제가 액티비티가 닫을 때마다 BaseActivity도 닫혔다 다시 열리면서 BluetoothService가 다시 초기화 되는 문제가 생겼다.

그래서 나도 그냥 Application 객체에다가 BluetoothLeService 변수를 넣어서 초기화했다.
좋은 코드 구조인지는 모르겠다;;

내가 원하는 곳에서 bindService()를 호출하면 서비스가 시작된다.
서비스 객체는 인텐트를 통해서 전달된다.

이 BluetoothLeService가 제대로 bind 되었는지(연결 되었는지) 확인하려면 ServiceConnection을 구현하여 콜백으로 연결 상태를 받아볼 수 있다.
(주의할 점 : BLE 데이터를 주고받을 때 Characteristic의 집합인 서비스와 헷갈리면 안된다!!)

public void initBle() {
Log.d(TAG, "initBle");
Intent gattServiceIntent = new Intent(this, BluetoothLeService.class);
boolean serviceConnected = bindService(gattServiceIntent, serviceConnection, Context.BIND_AUTO_CREATE);
Log.d(TAG, "initBle, serviceConnected: " + serviceConnected);


bleScanner = bleAdapter.getBluetoothLeScanner();
if (checkSelfPermission(Manifest.permission.BLUETOOTH_SCAN) == PackageManager.PERMISSION_GRANTED)
scanLeDevice();
}
난 Application 클래스에 initBle() 함수를 만들어서
BluetoothLeService를 바인드하는 함수를 만들어주었다.

Intent gattServiceIntent = new Intent(this, BluetoothLeService.class);
boolean serviceConnected = bindService(gattServiceIntent, serviceConnection, Context.BIND_AUTO_CREATE);
헷갈린다면 위 코드만 보면된다.
BluetoothLeService를 시작하는 코드다.

나 같은 경우에는 MainActivity가 시작하면 getApplicationContext().initBle() 로 BluetoothLeService를 시작했다.

private final ServiceConnection serviceConnection = new ServiceConnection() {

@Override
public void onServiceConnected(ComponentName name, IBinder service) {
Log.d(TAG, "onServiceConnected: ");
bleService = ((BluetoothLeService.LocalBinder) service).getService();
if (bleService != null) {
if (!bleService.initialize()) {
Log.e(TAG, "Unable to initialize Bluetooth");
}
// call functions on service to check connection and connect to devices

if (checkBlePermission(blePermissions)) {
if (foundDevice != null) bleService.connect(foundDevice.getAddress());
}
}
}

@Override
public void onServiceDisconnected(ComponentName name) {
Log.d(TAG, "onServiceDisconnected: ");
bleService = null;
}
};
그리고 마찬가지로 Application 클래스에 ServiceConnection 객체를 만들어서 BluetoothLeService가 제대로 바인드 되었는지 신호를 받는다.

블루투스 어댑터 설정하기

서비스를 시작하고나면 블루투스 어댑터가 필요하다.
public class BluetoothLeService extends Service {
public static final String TAG = "BluetoothLeService";


private BluetoothAdapter bleAdapter;


public boolean initialize() {
mContext = this;
bleAdapter = BluetoothAdapter.getDefaultAdapter();
bluetoothLeScanner = bleAdapter.getBluetoothLeScanner();
if (bleAdapter == null) {
Log.e(TAG, "initialize: Unable to obtain a BluetoothAdapter.");
return false;
}
return true;
} ......
}

initialize 함수를 통해서 블루투스 어댑터 객체를 얻자.

BLE 디바이스와 연결하기

BluetoothLeService가 시작된 후, BLE 디바이스와 BLE 연결을 할 수 있다.

public class BluetoothLeService extends Service {

..............

public boolean connect(final String address) {
if (bleAdapter == null || address == null) {
Log.w(TAG, "BluetoothAdapter not initialized or unspecified address.");
return false;
}

try {
final BluetoothDevice device = bleAdapter.getRemoteDevice(address);
// connect to the GATT server on the device
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.BLUETOOTH_CONNECT) != PackageManager.PERMISSION_GRANTED) {
return false;
}

bluetoothGatt = device.connectGatt(this, false, bluetoothGattCallback);
return true;
} catch (IllegalArgumentException exception) {
Log.w(TAG, "Device not found with provided address.");
return false;
}
} }
connect() 함수를 보면 bleAdapter(BluetoothAdapter 객체 저장한 변수)에서 getRemoteDevice()를 통해 Device 객체를 얻습니다.
getRemoteDevice()의 매개변수 address는 스캔할 때 내가 원하던 기기를 저장한 foundDevice 변수를 통해 전달하였습니다.

그리고 device.connectGatt() 함수를 통해서 ble 연결을 시도합니다.

GATT callback 선언하기

앱 구성요소(예: 액티비티)가 서비스한테 어떤 기기와 연결할 것을 요청하면 서비스는 BLE기기의 GATT 서버와 연결을 해야합니다.
이 과정에서 BluetoothGattCallback이 필요합니다.
BluetoothGattCallback의 역할은 ble기기와의 연결상태, service 찾기, characteristic 읽기, characteristic 쓰기에 대한 notification을 알려주는 것입니다.

아래 구현한 BluetoothGattCallback은 BluetoothLEService 클래스에 구현한 코드입니다.
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);
}


}
};
onConnectionStateChanged()는  ble기기의 GATT서버와의 연결상태에 변화가 있을 때 호출됩니다.
onServicesDiscovered()는 gatt.discoverServices()를 실행했을 때 호출됩니다.
onCharacteristicRead()는 gatt.readCharacteristic()을 실행했을 때 호출됩니다.
onCharacteristicWrite()는 gatt.writeCharacteristic()을 실행했을 때 호출됩니다.
onCharacteristicChanged()는 gatt.setCharacterisitcNotification()을 실행했을 때, 기기의 notification characteristic을 받는 콜백함수입니다.

저 같은 경우는 각 콜백함수에서 값을 받으면 브로드캐스트 리시버로 전달해주었습니다. ㅎ

GATT service와 연결하기

BluetoothGattCallback을 선언했으면 BluetoothLeService는 GATT 서버와 연결할 수 있습니다.
(GATT service = GATT server 라고 보시면 될거 같아요.)

public boolean connect(final String address) {
if (bleAdapter == null || address == null) {
Log.w(TAG, "BluetoothAdapter not initialized or unspecified address.");
return false;
}

try {
final BluetoothDevice device = bleAdapter.getRemoteDevice(address);
// connect to the GATT server on the device
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.BLUETOOTH_CONNECT) != PackageManager.PERMISSION_GRANTED) {
return false;
}

bluetoothGatt = device.connectGatt(this, false, bluetoothGattCallback);
return true;
} catch (IllegalArgumentException exception) {
Log.w(TAG, "Device not found with provided address.");
return false;
}
}
위 코드는 BluetoothLeService 클래스에 구현한 connect 함수입니다.
ble기기의 GATT 서버와 연결할 때는 connectGatt() 함수를 사용하구요.
매개변수로 Context 객체, autoConnect boolean flag, 그리고 앞서 구현한 BluetoothGattCallback이 필요합니다.

Broadcast updates

GATT 서버와 연결을 시도한 후 그 결과를 앱의 구성요소(예 : 액티비티)에 알려주어야 상화엥 맞게 코드를 진행하겠죠?
이때 안드로이드 공식문서에서는 BroadcastReceiver를 활용하는 것을 예시로 보여줍니다.

private void broadcastUpdate(final String action) {
final Intent intent = new Intent(action);
getApplicationContext().sendBroadcast(intent);
}
BluetoothLeService 클래스에 broadcastUpdate 함수를 선언해줍니다.

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);
}


}
};
그리고 앞에서 본 BluetoothGattCallback에서 broadcastUpdate 함수를 활용하여 변화된 Gatt서버의 상태를 전달합니다.

Broadcast 받기

broadcastUpdate()를 통해 GATT 서버의 상태를 알렸다면 받는 곳이 있어야겠죠?
저 같은 경우에는 Application 클래스에 브로드캐스트 리시버를 등록했어요.
안드로이드 공식 문서에서는 액티비티에 브로드 캐스트 리시버를 등록하는 예를 보여주고 있구요.


public class BTApplication extends Application {
private static final String TAG = "BTApplication";


String[] permissions = new String[]{
Manifest.permission.BLUETOOTH_CONNECT,
Manifest.permission.BLUETOOTH_SCAN,
Manifest.permission.BLUETOOTH_ADVERTISE,
};

/*
bjs: 새로운 기기는 ble모듈을 장착한다.
*/

public BluetoothLeService bleService;
private BluetoothAdapter bleAdapter = BluetoothAdapter.getDefaultAdapter();
public BluetoothDevice foundDevice;

boolean bleConnected;

private BluetoothLeScanner bleScanner;
private boolean scanning;
// Stops scanning after 10 seconds.
private static final long SCAN_PERIOD = 10000;
//

@Override
public void onCreate() {
// Log.d(TAG, "onCreate()");
super.onCreate();

/* bjs: 새로운 기기는 ble모듈을 장착한다. */
this.registerReceiver(gattUpdateReceiver, makeGattUpdateIntentFilter());
if (bleService != null && foundDevice != null) {
final boolean result = bleService.connect(foundDevice.getAddress());
Log.d(TAG, "Connect request result=" + result);
}
//
}



@Override
public void onTerminate() {
Log.d(TAG, "onTerminate()");

super.onTerminate();

if (mBtService != null)
mBtService.stop();

if (mBluetoothAdapter != null)
mBluetoothAdapter.disable();

if (receiver != null)
unregisterReceiver(receiver);

unregisterReceiver(gattUpdateReceiver);

//bjs : ble
unbindService(serviceConnection); //메모리 릭 방지
unregisterReceiver(receiver);
//
}



private final BroadcastReceiver gattUpdateReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
final String action = intent.getAction();
if (BluetoothLeService.ACTION_GATT_CONNECTED.equals(action)) {
bleConnected = true;
setBleConnected(true);
ActivityUtil.showToast(getBaseContext(), "Ble Connected");
//updateConnectionState(R.string.connected);
} else if (BluetoothLeService.ACTION_GATT_DISCONNECTED.equals(action)) {
bleConnected = false;
setBleConnected(false);
ActivityUtil.showToast(getBaseContext(), "Ble Disconnected");
//updateConnectionState(R.string.disconnected);
} else if (BluetoothLeService.ACTION_GATT_SERVICES_DISCOVERED.equals(action)) {
Log.d(TAG, "onReceive: Service Discovered");
//Show all the supported services and characteristics on the user interface
//displayGattServices(bleService.getSupportedGattServices());
}
}
};

private static IntentFilter makeGattUpdateIntentFilter() {
final IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(BluetoothLeService.ACTION_GATT_CONNECTED);
intentFilter.addAction(BluetoothLeService.ACTION_GATT_DISCONNECTED);
intentFilter.addAction(BluetoothLeService.ACTION_GATT_SERVICES_DISCOVERED);
return intentFilter;
}

protected Boolean checkBlePermission(String[] permissions) {
for (String permission : permissions) {
if (checkSelfPermission(permission) == PackageManager.PERMISSION_DENIED) return false;
}
return true;
}


public boolean isBleConnected() {
return bleConnected;
}

public void setBleConnected(boolean bleConnected) {
this.bleConnected = bleConnected;
}
}
일단 makeGattUpdateIntentFilter() 함수를 활용하여 BroadcastReceiver를 선언해줍니다.
onCreate()에서 브로드캐스트 리시버를 등록해주구요.
onTerminate()에서 브로드캐스트 리시버를 등록해제시킵니다.

이 글은 BLE 연결에 대해 다루기때문에 브로드캐스트 리시버에 대해서는 자세히 다루지 않을게요.

제 블로그에 브로드캐스트 리시버에 대한 글을 다룬 적이 있어요.
자세한 내용은 아래 링크를 참고해주세요.👇

Close GATT connection

GATT 서버와의 연결을 끊는 과정도 역시 중요하죠?

class BluetoothService extends Service {

...

      @Override
      public boolean onUnbind(Intent intent) {
          close();
          return super.onUnbind(intent);
      }

      private void close() {
          if (bluetoothGatt == null) {
              Return;
          }
          bluetoothGatt.close();
          bluetoothGatt = null;
      }
}
 위 코드는 안드로이드 공식문서에 있는 코드를 그대로 가져온 것입니다.
서비스 클래스에서 onUnbind() 함수에서 close() 함수를 호출하여 GATT 서버와의 연결을 해제합니다.

여기까지 BLE연결에 대한 개념, 스캔, GATT 서버와의 연결까지 다루어봤습니다.

다음 글에서는 BLE기기와 데이터를 주고받는 방법에 대해 알아보도록 하겠습니다.




















































Share:
Read More