본문 바로가기
프로그래밍/Android, iOS

안드로이드 BLE 통신(공식문서 번역) 1

by YuminK 2023. 7. 23.

작성: 2023 7 21

https://developer.android.com/guide/topics/connectivity/bluetooth/ble-overview

 

일반적인 블루투스과 다르게 저전력 사용을 목적으로 한다.

) 접근 센서, 심장 맥박 측정기, 피트니스 디바이스 등

 

BLE 필요 권한(manifest)

https://developer.android.com/guide/topics/connectivity/bluetooth/permissions#java

<uses-permission android:name="android.permission.BLUETOOTH"
    android:maxSdkVersion="30" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"
    android:maxSdkVersion="30" />

<!-- Needed only if your app looks for Bluetooth devices.
    If your app doesn't use Bluetooth scan results to derive physical
    location information, you can strongly assert that your app
    doesn't derive physical location. -->
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" />

<!-- Needed only if your app makes the device discoverable to Bluetooth
     devices. -->
<uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE" />

<!-- Needed only if your app uses Bluetooth scan results to derive physical location. -->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />

<!-- If your app supports a service and can run on Android 10 (API level 29) or Android 11 -->
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />


<!-- Needed only if your app communicates with already-paired Bluetooth
 devices. -->
    <uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
블루투스 지원 여부 확인
boolean bluetoothAvailable = getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH);

블루투스 BLE 여부 확인
boolean bluetoothLEAvailable = getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE);

 

API별 요청 권한 코드

void requestAllPermissions() {
    if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
        requestPermissions(new String[] {
                        Manifest.permission.ACCESS_FINE_LOCATION,
                        Manifest.permission.BLUETOOTH,
                        Manifest.permission.BLUETOOTH_SCAN,
                        Manifest.permission.BLUETOOTH_ADVERTISE,
                        Manifest.permission.BLUETOOTH_CONNECT
                }, REQUEST_ALL);
    } else if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
        requestPermissions(new String[] {
                Manifest.permission.ACCESS_FINE_LOCATION,
                Manifest.permission.BLUETOOTH,
                Manifest.permission.ACCESS_BACKGROUND_LOCATION
        }, REQUEST_ALL);
    } else {
        requestPermissions(new String[] {
                Manifest.permission.ACCESS_FINE_LOCATION,
                Manifest.permission.BLUETOOTH,
        }, REQUEST_ALL);
    }
}

 

Generic Attribute Profile(GATT)

GATT 프로필은 일반적인 상세 데이터이다. 현재 모든 BLE 프로필은 GATT를 기반으로 한다.

 

Attribute Protocol(ATT)

GATTATT를 기반으로 한다. GATT/ATT라고도 한다. BLE 디바이스에서 최적으로 돌아가기 위해 설계되었다. 128비트 형식인 UUID로 고유하게 식별됩니다. ATT가 전송하는 속성은 캐릭터(Characteristics)와 서비스(Services)로 구성된다.

 

Characteristic

데이터 값과 0-n 캐릭터 값을 표현하는 Descriptors값을 가진다. 클래스와 유사한 유형으로 생각할 수 있다.

 

Descriptor

캐릭터 값을 표현하는 정의된 속성이다. 사람이 읽을 수 있는 설명으로 표현될 수 있다.

 

Service

캐릭터의 집합

 

Service 내부에 Characters, Characters내부에 Descriptors가 있는 형태이다.

 

역할과 책임

GATT서버 대 GATT 클라이언트. 두 장치가 서로 통신하는 방법을 결정한다. 안드로이드 폰과 활동 추적기가 있다고 할 때, 폰은 중심 역할을 지원하고 활동 추적기는 주변 역할을 지원한다. 이때 중앙 장치만 2가지가 있거나 주변 장치만 2개가 존재하는 경우 서로 통신할 수 없다. 일단 통신이 되면, GATT 메타데이터를 전송한다. 전송하는 데이터의 역할에 따라 둘 중 하나가 서버 역할을 할 수 있다. 예를 들어 활동 추적기가 센서 데이터를 전화기에 보고하려는 경우 활동 추적기가 서버 역할을 하는 것이 합리적일 수 있습니다. 활동 추적기가 전화기에서 업데이트를 수신하려는 경우 전화기가 서버 역할을 하는 것이 합리적일 수 있습니다

 

BLE 기기 찾기

startScan 메서드를 통해 찾을 수 있다. ScanCallback을 파라미터로 받는다. 스캐팅은 배터리 사용 때문에 가이드라인을 따라야 한다.

1.     원하는 디바이스를 찾으면 스캐닝을 종료한다.

2.     절대 스캔을 루프로 돌리지 마라. 스캔 제한 시간을 설정해라.

private BluetoothLeScanner bluetoothLeScanner = bluetoothAdapter.getBluetoothLeScanner();
private boolean scanning;
private Handler handler = new Handler();

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

private void scanLeDevice() {
    if (!scanning) {
        // Stops scanning after a predefined scan period.
        handler.postDelayed(new Runnable() {
            @Override
            public void run() {
                scanning = false;
                bluetoothLeScanner.stopScan(leScanCallback);
            }
        }, SCAN_PERIOD);

        scanning = true;
        bluetoothLeScanner.startScan(leScanCallback);
    } else {
        scanning = false;
        bluetoothLeScanner.stopScan(leScanCallback);
    }
}

 

특정 타입의 주변 기기를 찾으려는 경우 startScan(List<ScanFilter>, ScanSettings, ScanCallback)함수를 사용할 수 있다. ScanFilter는 찾는 기기를 제한한다.

ScanSettings.Builder mScanSettings = new ScanSettings.Builder();
mScanSettings.setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY);
ScanSettings scanSettings = mScanSettings.build();

List<ScanFilter> scanFilters = new Vector<>();
ScanFilter.Builder scanFilter = new ScanFilter.Builder();

// 원하는 서비스 UUID만 필터링한다.
scanFilter.setServiceUuid(new ParcelUuid(RX_SERVICE_UUID));
//  scanFilter.setDeviceAddress("BC:54:51:33:24:91"); // 블루투스 Mac주소
ScanFilter scan = scanFilter.build();
scanFilters.add(scan);

// mBLEScanner.startScan(mScanCallback);
mBLEScanner.startScan(scanFilters, scanSettings, mScanCallback);

 

디바이스 스캔 callback

private LeDeviceListAdapter leDeviceListAdapter = new LeDeviceListAdapter();

// Device scan callback.
private ScanCallback leScanCallback =
        new ScanCallback() {
            @Override
            public void onScanResult(int callbackType, ScanResult result) {
                super.onScanResult(callbackType, result);
                leDeviceListAdapter.addDevice(result.getDevice());
                leDeviceListAdapter.notifyDataSetChanged();
            }
        };

 

GATT 서버 연결

GATT 서버에 연결하기 위해 connectGatt 메소드를 이용한다.

bluetoothGatt = device.connectGatt(this, false, gattCallback);

 

BLE 디바이스에 의해 호스트된 GATT 서버에 연결한다. GattCallback GATT 클라이언트에 결과를 배달하기 위해 사용된다. BluetoothLService를 사용하여 BLE API를 사용한다. Bound Service를 이용하여 실행된다. BluetoothLService Binder를 필요로 한다.

class BluetoothLeService extends Service {

    private Binder binder = new LocalBinder();

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return binder;
    }

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

bindService 메소드를 통해 서비스를 시작할 수 있다. Intent service에 넘기는 것, ServiceConnection은 연결과 연결 끊김 이벤트 처리를 위해 사용된다.

 

BluetoothAdapter 설정

한번 서비스가 바운드 되면, bluetoothAdapter에 접근할 필요가 있다. 디바이스에서 BLE서비스가 가능한지 체크할 수 있다.

class BluetoothLeService extends Service {

    public static final String TAG = "BluetoothLeService";

    private BluetoothAdapter bluetoothAdapter;

    public boolean initialize() {
        bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
        if (bluetoothAdapter == null) {
            Log.e(TAG, "Unable to obtain a BluetoothAdapter.");
            return false;
        }
        return true;
    }

    ...
}

 

DeviceControlActivity에서는 ServiceConnection 내부에서 initialize 메소드를 호출한다. 블루투스 기능을 사용하는데 요구되는 기능이 비활성화된 상태거나 블루투스를 지원하지 않으면 false를 반환한다.

class DeviceControlsActivity extends AppCompatActivity {

    private ServiceConnection serviceConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            bluetoothService = ((LocalBinder) service).getService();
            if (bluetoothService != null) {
                if (!bluetoothService.initialize()) {
                    Log.e(TAG, "Unable to initialize Bluetooth");
                    finish();
                }
                // perform device connection
            }
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            bluetoothService = null;
        }
    };

    ...
}

일단 BluetoothService가 초기화되면, BLE 디바이스에 연결될 수 있다. 액티비티는 디바이스 주소를 서비스에 보낸다. 서비스는 bluetotoheAdaptergetRemoteDevice 메소드를 통해 디바이스에 접근한다. 주소를 통해 디바이스를 찾지 못하면 IllegalArgumentException 오류를 발생시킨다.

 

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

    try {
        final BluetoothDevice device = bluetoothAdapter.getRemoteDevice(address);
    } catch (IllegalArgumentException exception) {
        Log.w(TAG, "Device not found with provided address.");
        return false;
    }
    // connect to the GATT server on the device
}

 

서비스가 초기화될 때, connect 메소드를 호출한다. BLE 디바이스 주소를 넘겨야 한다.

private ServiceConnection serviceConnection = new ServiceConnection() {
    @Override
    public void onServiceConnected(ComponentName name, IBinder service) {
        bluetoothService = ((LocalBinder) service).getService();
        if (bluetoothService != null) {
            if (!bluetoothService.initialize()) {
                Log.e(TAG, "Unable to initialize Bluetooth");
                finish();
            }
            // perform device connection
            bluetoothService.connect(deviceAddress);
        }
    }

    @Override
    public void onServiceDisconnected(ComponentName name) {
        bluetoothService = null;
    }
};

댓글