Bluetooth low energy

Bluetooth low energy 개요

Android 4.3 (API 레벨 18)부터 중심 역할에 BLE (Bluetooth Low Energy)에 대한 기본 제공 플랫폼 지원을 도입하고 앱이 장치를 검색하고 서비스를 쿼리하고 정보를 전송하는 데 사용할 수있는 API를 제공합니다.

클래식 Bluetooth와 달리 BLE (Bluetooth Low Energy)는 전력 소비를 크게 줄 이도록 설계되었습니다.

이를 통해 Android 앱은 근접 센서, 심박수 모니터 및 피트니스 장치와 같이 더 엄격한 전원 요구 사항이있는 BLE 장치와 통신 할 수 있습니다.

GATT(Generic Attribute Profile)

GATT 프로파일은 BLE 링크를 통해 “attributes”라고하는 짧은 데이터 조각을 송수신하기위한 일반 사양입니다.

현재 모든 저에너지 애플리케이션 프로파일은 GATT를 기반으로합니다.

Bluetooth SIG는 저에너지 장치에 대한 많은 프로파일을 정의합니다.

프로파일은 장치가 특정 응용 프로그램에서 작동하는 방식에 대한 사양입니다.

장치는 둘 이상의 프로필을 구현할 수 있습니다. 예를 들어, 장치에는 심박수 모니터와 배터리 수준 감지기가 포함될 수 있습니다.

Attribute Protocol (ATT)

GATT는 ATT (Attribute Protocol) 위에 구축됩니다. 이것을 GATT / ATT라고도합니다.

ATT는 BLE 장치에서 실행되도록 최적화되었습니다. 이를 위해 가능한 적은 바이트를 사용합니다.

각 attributes는 정보를 고유하게 식별하는 데 사용되는 문자열 ID의 표준화 된 128 비트 형식 인 UUID (Universally Unique Identifier)로 고유하게 식별됩니다.

ATT에 의해 전송 된 attributes는 characteristics 및 services로 형식화됩니다.

GATT level

Characteristic

Characteristic에는 단일 값과 특성 값을 설명하는 0-n 설명자가 포함됩니다.

Characteristic은 클래스와 유사한 유형으로 생각할 수 있습니다.

Descriptor

Descriptor는 특성 값을 설명하는 정의 된 속성입니다.

예를 들어 Descriptor는 사람이 읽을 수있는 설명, 특성 값의 허용 가능한 범위 또는 특성 값에 특정한 측정 단위를 지정할 수 있습니다.

Service

서비스는 characteristics의 모음입니다. 예를 들어 “심박수 측정”과 같은 characteristics을 포함하는 “심박수 모니터”라는 서비스가있을 수 있습니다.

bluetooth.org에서 기존 GATT 기반 프로파일 및 서비스 목록을 찾을 수 있습니다.

Roles and responsibilities

Central vs. peripheral(주변장치)

이것은 BLE 연결 자체에 적용됩니다.

중앙 역할의 장치가 advertisement를 검색하고 주변 역할의 장치가 advertisement를 만듭니다.

GATT server vs. GATT client

연결이 설정되면 두 장치가 서로 통신하는 방법을 결정합니다.

차이점을 이해하려면 Android 전화와 BLE 장치 인 활동 추적기가 있다고 가정하세요.

전화는 중심적인 역할을 지원합니다. 활동 추적기는 주변 장치 역할을 지원합니다.
(BLE 연결을 설정하려면 주변 장치 만 지원하는 두 가지 또는 중앙 통신 장치 만 지원하는 두 가지 중 하나).

전화와 활동 추적기가 연결되면 GATT 메타 데이터를 서로에게 전송하기 시작합니다.

전송하는 데이터의 종류에 따라 하나 또는 다른 서버가 서버로 작동 할 수 있습니다.

예를 들어, 활동 추적기가 센서 데이터를 전화로보고하려는 경우 활동 추적기가 서버 역할을하는 것이 적합 할 수 있습니다.

활동 추적기가 전화기에서 업데이트를 수신하려는 경우 전화기가 서버 역할을하는 것이 좋습니다.

BLE permissions

응용 프로그램에서 Bluetooth 기능을 사용하려면 Bluetooth 권한 BLUETOOTH를 선언해야합니다.

연결 요청, 연결 수락 및 데이터 전송과 같은 Bluetooth 통신을 수행하려면이 권한이 필요합니다.

앱이 장치 검색을 시작하거나 Bluetooth 설정을 조작하도록하려면 BLUETOOTH_ADMIN 권한도 선언해야합니다.

1
2
<uses-permission android:name="android.permission.BLUETOOTH"/>
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>

앱을 BLE 지원 장치에서만 사용할 수 있다고 선언하려면 앱 매니페스트에 다음을 포함하십시오.

1
<uses-feature android:name="android.hardware.bluetooth_le" android:required="true"/>

그러나 BLE를 지원하지 않는 장치에서 앱을 사용하려면 앱의 매니페스트에이 요소를 포함시켜야하지만 required = “false”를 설정해야합니다.

그런 다음 런타임에 PackageManager.hasSystemFeature ()를 사용하여 BLE 가용성을 확인할 수 있습니다.

1
2
3
4
5
6
// Use this check to determine whether BLE is supported on the device. Then
// you can selectively disable BLE-related features.
if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) {
Toast.makeText(this, R.string.ble_not_supported, Toast.LENGTH_SHORT).show();
finish();
}

Set up BLE

애플리케이션이 BLE를 통해 통신하기 전에 디바이스에서 BLE가 지원되는지 확인하고, 가능하면 사용 가능해야합니다.

이 점검은 <uses-feature … />가 false로 설정된 경우에만 필요합니다.

BLE가 지원되지 않으면 BLE 기능을 정상적으로 비활성화해야합니다.

BLE가 지원되지만 비활성화 된 경우 응용 프로그램을 종료하지 않고 사용자에게 Bluetooth를 활성화하도록 요청할 수 있습니다.

이 설정은 BluetoothAdapter를 사용하여 두 단계로 수행됩니다.

Get the BluetoothAdapter

BluetoothAdapter는 모든 Bluetooth 활동에 필요합니다. BluetoothAdapter는 장치 자체의 Bluetooth 어댑터 (Bluetooth 라디오)를 나타냅니다.

전체 시스템에 하나의 Bluetooth 어댑터가 있으며이 객체를 사용하여 응용 프로그램과 상호 작용할 수 있습니다.

아래는 어댑터를 얻는 방법을 보여줍니다. 이 접근 방식은 getSystemService ()를 사용하여 BluetoothManager 인스턴스를 리턴 한 다음 어댑터를 얻는 데 사용됩니다.

Android 4.3 (API 레벨 18)에 BluetoothManager가 도입되었습니다.

1
2
3
4
5
6
private BluetoothAdapter bluetoothAdapter;
...
// Initializes Bluetooth adapter.
final BluetoothManager bluetoothManager =
(BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
bluetoothAdapter = bluetoothManager.getAdapter();

Enable Bluetooth

다음으로, 블루투스가 활성화되어 있는지 확인해야합니다.

isEnabled ()를 호출하여 현재 Bluetooth가 활성화되어 있는지 확인하십시오.

이 방법이 false를 반환하면 Bluetooth가 비활성화 된 것입니다.

다음은 블루투스가 활성화되어 있는지 확인합니다. 그렇지 않은 경우 사용자가 설정으로 이동하여 Bluetooth를 활성화하라는 오류 메시지가 표시됩니다.

1
2
3
4
5
6
// Ensures Bluetooth is available on the device and it is enabled. If not,
// displays a dialog requesting user permission to enable Bluetooth.
if (bluetoothAdapter == null || !bluetoothAdapter.isEnabled()) {
Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);
}

BLE 장치 찾기

BLE 장치를 찾으려면 startLeScan() 메소드를 사용하십시오.

이 메소드는 BluetoothAdapter.LeScanCallback을 매개 변수로 사용합니다.

이 콜백은 스캔 결과가 리턴되는 방식이므로 구현해야합니다.

스캔은 배터리를 많이 사용하므로 다음 지침을 준수해야합니다.

  • 원하는 장치를 찾으면 스캔을 중지하십시오.

  • 루프를 스캔하지 말고 스캔 시간 제한을 설정하십시오. 이전에 사용 가능했던 장치가 범위를 벗어 났으며 스캔을 계속하면 배터리가 소진됩니다.

다음은 스캔을 시작하고 중지하는 방법을 보여줍니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
/**
* Activity for scanning and displaying available BLE devices.
*/
public class DeviceScanActivity extends ListActivity {

private BluetoothAdapter bluetoothAdapter;
private boolean mScanning;
private Handler handler;

// Stops scanning after 10 seconds.
private static final long SCAN_PERIOD = 10000;
...
private void scanLeDevice(final boolean enable) {
if (enable) {
// Stops scanning after a pre-defined scan period.
handler.postDelayed(new Runnable() {
@Override
public void run() {
mScanning = false;
bluetoothAdapter.stopLeScan(leScanCallback);
}
}, SCAN_PERIOD);

mScanning = true;
bluetoothAdapter.startLeScan(leScanCallback);
} else {
mScanning = false;
bluetoothAdapter.stopLeScan(leScanCallback);
}
...
}
...
}

특정 유형의 주변 장치 만 검색하려는 경우 대신 startLeScan (UUID [], BluetoothAdapter.LeScanCallback)을 호출하여 앱이 지원하는 GATT 서비스를 지정하는 UUID 객체 배열을 제공 할 수 있습니다.

다음은 BLE 스캔 결과를 제공하는 데 사용되는 인터페이스 인 BluetoothAdapter.LeScanCallback의 구현입니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
private LeDeviceListAdapter leDeviceListAdapter;
...
// Device scan callback.
private BluetoothAdapter.LeScanCallback leScanCallback =
new BluetoothAdapter.LeScanCallback() {
@Override
public void onLeScan(final BluetoothDevice device, int rssi,
byte[] scanRecord) {
runOnUiThread(new Runnable() {
@Override
public void run() {
leDeviceListAdapter.addDevice(device);
leDeviceListAdapter.notifyDataSetChanged();
}
});
}
};

Bluetooth에 설명 된대로 Bluetooth LE 장치 만 검색하거나 Classic Bluetooth 장치 만 검색 할 수 있습니다. Bluetooth LE와 클래식 장치를 동시에 검색 할 수 없습니다.

Connect to a GATT server

BLE 장치와 상호 작용하는 첫 번째 단계는 장치, 특히 장치의 GATT 서버에 연결하는 것입니다.

BLE 장치의 GATT 서버에 연결하려면 connectGatt() 메소드를 사용하십시오.

이 메소드는 Context 객체, autoConnect(BLE 장치가 사용 가능 해지면 자동으로 연결할지 여부를 나타내는 부울) 및 BluetoothGattCallback에 대한 참조의 세 가지 매개 변수를 사용합니다.

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

이것은 BLE 장치가 호스팅하는 GATT 서버에 연결하고 BluetoothGatt 인스턴스를 반환 한 다음 GATT 클라이언트 작업을 수행하는 데 사용할 수 있습니다.

발신자 (Android 앱)는 GATT 클라이언트입니다.

BluetoothGattCallback은 연결 상태 및 추가 GATT 클라이언트 작업과 같은 결과를 클라이언트에 전달하는 데 사용됩니다.

아래 예에서 BLE 앱은 장치에서 지원하는 GATT 서비스 및 특성을 연결, 표시 및 표시하는 활동 (DeviceControlActivity)을 제공합니다.

사용자 입력에 따라이 활동은 BluetoothLeService라는 서비스와 통신하며, 이 서비스는 Android BLE API를 통해 BLE 장치와 상호 작용합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
// A service that interacts with the BLE device via the Android BLE API.
public class BluetoothLeService extends Service {
private final static String TAG = BluetoothLeService.class.getSimpleName();

private BluetoothManager bluetoothManager;
private BluetoothAdapter bluetoothAdapter;
private String bluetoothDeviceAddress;
private BluetoothGatt bluetoothGatt;
private int connectionState = STATE_DISCONNECTED;

private static final int STATE_DISCONNECTED = 0;
private static final int STATE_CONNECTING = 1;
private static final int STATE_CONNECTED = 2;

public final static String ACTION_GATT_CONNECTED =
"com.example.bluetooth.le.ACTION_GATT_CONNECTED";
public final static String ACTION_GATT_DISCONNECTED =
"com.example.bluetooth.le.ACTION_GATT_DISCONNECTED";
public final static String ACTION_GATT_SERVICES_DISCOVERED =
"com.example.bluetooth.le.ACTION_GATT_SERVICES_DISCOVERED";
public final static String ACTION_DATA_AVAILABLE =
"com.example.bluetooth.le.ACTION_DATA_AVAILABLE";
public final static String EXTRA_DATA =
"com.example.bluetooth.le.EXTRA_DATA";

public final static UUID UUID_HEART_RATE_MEASUREMENT =
UUID.fromString(SampleGattAttributes.HEART_RATE_MEASUREMENT);

// Various callback methods defined by the BLE API.
private final BluetoothGattCallback gattCallback =
new BluetoothGattCallback() {
@Override
public void onConnectionStateChange(BluetoothGatt gatt, int status,
int newState) {
String intentAction;
if (newState == BluetoothProfile.STATE_CONNECTED) {
intentAction = ACTION_GATT_CONNECTED;
connectionState = STATE_CONNECTED;
broadcastUpdate(intentAction);
Log.i(TAG, "Connected to GATT server.");
Log.i(TAG, "Attempting to start service discovery:" +
bluetoothGatt.discoverServices());

} else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
intentAction = ACTION_GATT_DISCONNECTED;
connectionState = STATE_DISCONNECTED;
Log.i(TAG, "Disconnected from GATT server.");
broadcastUpdate(intentAction);
}
}

@Override
// New services discovered
public void onServicesDiscovered(BluetoothGatt gatt, int status) {
if (status == BluetoothGatt.GATT_SUCCESS) {
broadcastUpdate(ACTION_GATT_SERVICES_DISCOVERED);
} else {
Log.w(TAG, "onServicesDiscovered received: " + status);
}
}

@Override
// Result of a characteristic read operation
public void onCharacteristicRead(BluetoothGatt gatt,
BluetoothGattCharacteristic characteristic,
int status) {
if (status == BluetoothGatt.GATT_SUCCESS) {
broadcastUpdate(ACTION_DATA_AVAILABLE, characteristic);
}
}
...
};
...
}

특정 콜백이 트리거되면 적절한 broadcastUpdate() 헬퍼 메소드를 호출하고 액션을 전달합니다.

이 섹션의 데이터 구문 분석은 Bluetooth 심박수 측정 프로파일 사양에 따라 수행됩니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
private void broadcastUpdate(final String action) {
final Intent intent = new Intent(action);
sendBroadcast(intent);
}

private void broadcastUpdate(final String action,
final BluetoothGattCharacteristic characteristic) {
final Intent intent = new Intent(action);

// This is special handling for the Heart Rate Measurement profile. Data
// parsing is carried out as per profile specifications.
if (UUID_HEART_RATE_MEASUREMENT.equals(characteristic.getUuid())) {
int flag = characteristic.getProperties();
int format = -1;
if ((flag & 0x01) != 0) {
format = BluetoothGattCharacteristic.FORMAT_UINT16;
Log.d(TAG, "Heart rate format UINT16.");
} else {
format = BluetoothGattCharacteristic.FORMAT_UINT8;
Log.d(TAG, "Heart rate format UINT8.");
}
final int heartRate = characteristic.getIntValue(format, 1);
Log.d(TAG, String.format("Received heart rate: %d", heartRate));
intent.putExtra(EXTRA_DATA, String.valueOf(heartRate));
} else {
// For all other profiles, writes the data formatted in HEX.
final byte[] data = characteristic.getValue();
if (data != null && data.length > 0) {
final StringBuilder stringBuilder = new StringBuilder(data.length);
for(byte byteChar : data)
stringBuilder.append(String.format("%02X ", byteChar));
intent.putExtra(EXTRA_DATA, new String(data) + "\n" +
stringBuilder.toString());
}
}
sendBroadcast(intent);
}

DeviceControlActivity로 돌아 가면이 이벤트는 BroadcastReceiver에 의해 처리됩니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// Handles various events fired by the Service.
// ACTION_GATT_CONNECTED: connected to a GATT server.
// ACTION_GATT_DISCONNECTED: disconnected from a GATT server.
// ACTION_GATT_SERVICES_DISCOVERED: discovered GATT services.
// ACTION_DATA_AVAILABLE: received data from the device. This can be a
// result of read or notification operations.
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)) {
connected = true;
updateConnectionState(R.string.connected);
invalidateOptionsMenu();
} else if (BluetoothLeService.ACTION_GATT_DISCONNECTED.equals(action)) {
connected = false;
updateConnectionState(R.string.disconnected);
invalidateOptionsMenu();
clearUI();
} else if (BluetoothLeService.
ACTION_GATT_SERVICES_DISCOVERED.equals(action)) {
// Show all the supported services and characteristics on the
// user interface.
displayGattServices(bluetoothLeService.getSupportedGattServices());
} else if (BluetoothLeService.ACTION_DATA_AVAILABLE.equals(action)) {
displayData(intent.getStringExtra(BluetoothLeService.EXTRA_DATA));
}
}
};

Read BLE attributes

Android 앱이 GATT 서버에 연결되고 서비스를 발견하면 지원되는 경우 속성을 읽고 쓸 수 있습니다.

예를 들어 아래 예는 서버의 서비스와 특성을 반복하여 UI에 표시합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
public class DeviceControlActivity extends Activity {
...
// Demonstrates how to iterate through the supported GATT
// Services/Characteristics.
// In this sample, we populate the data structure that is bound to the
// ExpandableListView on the UI.
private void displayGattServices(List<BluetoothGattService> gattServices) {
if (gattServices == null) return;
String uuid = null;
String unknownServiceString = getResources().
getString(R.string.unknown_service);
String unknownCharaString = getResources().
getString(R.string.unknown_characteristic);
ArrayList<HashMap<String, String>> gattServiceData =
new ArrayList<HashMap<String, String>>();
ArrayList<ArrayList<HashMap<String, String>>> gattCharacteristicData
= new ArrayList<ArrayList<HashMap<String, String>>>();
mGattCharacteristics =
new ArrayList<ArrayList<BluetoothGattCharacteristic>>();

// Loops through available GATT Services.
for (BluetoothGattService gattService : gattServices) {
HashMap<String, String> currentServiceData =
new HashMap<String, String>();
uuid = gattService.getUuid().toString();
currentServiceData.put(
LIST_NAME, SampleGattAttributes.
lookup(uuid, unknownServiceString));
currentServiceData.put(LIST_UUID, uuid);
gattServiceData.add(currentServiceData);

ArrayList<HashMap<String, String>> gattCharacteristicGroupData =
new ArrayList<HashMap<String, String>>();
List<BluetoothGattCharacteristic> gattCharacteristics =
gattService.getCharacteristics();
ArrayList<BluetoothGattCharacteristic> charas =
new ArrayList<BluetoothGattCharacteristic>();
// Loops through available Characteristics.
for (BluetoothGattCharacteristic gattCharacteristic :
gattCharacteristics) {
charas.add(gattCharacteristic);
HashMap<String, String> currentCharaData =
new HashMap<String, String>();
uuid = gattCharacteristic.getUuid().toString();
currentCharaData.put(
LIST_NAME, SampleGattAttributes.lookup(uuid,
unknownCharaString));
currentCharaData.put(LIST_UUID, uuid);
gattCharacteristicGroupData.add(currentCharaData);
}
mGattCharacteristics.add(charas);
gattCharacteristicData.add(gattCharacteristicGroupData);
}
...
}
...
}

Receive GATT notifications

장치의 특정 characteristic가 변경 될 때 BLE 앱에 알림을 요청하는 것이 일반적입니다.

아래 예는 setCharacteristicNotification() 메소드를 사용하여 특성에 대한 알림을 설정하는 방법을 보여줍니다.

1
2
3
4
5
6
7
8
9
10
private BluetoothGatt bluetoothGatt;
BluetoothGattCharacteristic characteristic;
boolean enabled;
...
bluetoothGatt.setCharacteristicNotification(characteristic, enabled);
...
BluetoothGattDescriptor descriptor = characteristic.getDescriptor(
UUID.fromString(SampleGattAttributes.CLIENT_CHARACTERISTIC_CONFIG));
descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
bluetoothGatt.writeDescriptor(descriptor);

특성에 대한 알림이 활성화되면 원격 장치에서 특성이 변경되면 onCharacteristicChanged() 콜백이 트리거됩니다.

1
2
3
4
5
6
@Override
// Characteristic notification
public void onCharacteristicChanged(BluetoothGatt gatt,
BluetoothGattCharacteristic characteristic) {
broadcastUpdate(ACTION_DATA_AVAILABLE, characteristic);
}

Close the client app

앱이 BLE 장치 사용을 마치면 close ()를 호출하여 시스템에서 리소스를 적절하게 해제 할 수 있습니다.

1
2
3
4
5
6
7
public void close() {
if (bluetoothGatt == null) {
return;
}
bluetoothGatt.close();
bluetoothGatt = null;
}
공유하기