BLE - Sizuha/devdog GitHub Wiki
Bluetooth Low Energy
- Peripheral: サービスを提供する側。基本的にここからデータを発信する。
- Characteristic: Peripheralの中のサービス区分。実際、これと送・受信することになる。
- Central: Peripheralをスキャンする側。複数のPeripheralと接続可能。
Peripheralが発信する信号、Centralがこの信号を拾う
アドバタイズデータの例
No. | Field | Bytes | 備考 |
---|---|---|---|
1 | Length | 1 | |
AD Type | 1 | 0x01 | |
Flags | 1 | 必須 | |
2 | Length | 1 | |
AD Type | 1 | 0x21 | |
Service Data UUID | 16 | 自由に設定 | |
Service Data | 10 | 自由に設定 | |
合計 | 31 |
- アドバタイズデータは31Bytes内にすること
- 各FieldにはLengthとAD Type情報が必要
- UUIDの場合、16-bit/32-bit/128-bitの3種類がある
- UUIDの長さはAD Typeによって決められる
- 16-bit/32-bitの短縮型UUIDはBluetooth標準で決められたものを入れる
- AD Type/UUIDなどの参考資料
スキャン応答データの例
No. | Field | Bytes | 備考 |
---|---|---|---|
1 | Length | 1 | |
AD Type | 1 | 0x21 | |
Service Data UUID | 16 | 自由に設定 | |
Service Data | 13 | 自由に設定 | |
合計 | 31 |
Bluetooth用のUUIDの短縮型
- 16-bit: 0000XXXX-0000-1000-8000-00805f9b34fb
- 32-bit: AAAABBBB-0000-1000-8000-00805f9b34fb
mBluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
mBluetoothAdapter = mBluetoothManager.getAdapter();
setupGattServer();
private void setupGattServer() {
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.BLUETOOTH_CONNECT) != PackageManager.PERMISSION_GRANTED) {
return;
}
if (mBluetoothGattServer != null) {
mBluetoothGattServer.close();
}
mBluetoothGattServer = mBluetoothManager.openGattServer(this, gattServerCallback);
BluetoothGattService service = new BluetoothGattService(SERVICE_UUID, BluetoothGattService.SERVICE_TYPE_PRIMARY);
// Characteristicの登録
BluetoothGattCharacteristic characteristic = new BluetoothGattCharacteristic(
CHARACTERISTIC_UUID,
BluetoothGattCharacteristic.PROPERTY_READ | BluetoothGattCharacteristic.PROPERTY_WRITE,
BluetoothGattCharacteristic.PERMISSION_READ | BluetoothGattCharacteristic.PERMISSION_WRITE
);
service.addCharacteristic(characteristic);
mBluetoothGattServer.addService(service);
}
private final BluetoothGattServerCallback gattServerCallback = new BluetoothGattServerCallback() {
@SuppressLint("MissingPermission")
@Override
public void onConnectionStateChange(BluetoothDevice device, int status, int newState) {
if (newState == BluetoothProfile.STATE_CONNECTED) {
mDevice = device;
mBluetoothGattServer.connect(mDevice, false);
// 接続確立時にアドバタイズを停止
stopAdvertising();
} else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
//セントラルからの切断
}
}
@Override
public void onCharacteristicReadRequest(BluetoothDevice device, int requestId, int offset, BluetoothGattCharacteristic characteristic) {
// CentralからのREAD_REQ
if (!CHARACTERISTIC_UUID.equals(characteristic.getUuid()) ||
ActivityCompat.checkSelfPermission(getApplicationContext(), Manifest.permission.BLUETOOTH_CONNECT) != PackageManager.PERMISSION_GRANTED
) {
return;
}
String responseData = null;
// TODO 返信するデータを作成
if (responseData != null) {
mBluetoothGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, offset, partBytes);
} else {
mBluetoothGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, 0, null);
}
}
@Override
public void onCharacteristicWriteRequest(BluetoothDevice device, int requestId, BluetoothGattCharacteristic characteristic, boolean preparedWrite, boolean responseNeeded, int offset, byte[] value) {
// CentralからのWRITE
if (CHARACTERISTIC_UUID.equals(characteristic.getUuid())) {
return;
}
if (value == null) {
return;
}
final String receivedData = new String(value);
if (responseNeeded) {
int responseStatus = BluetoothGatt.GATT_SUCCESS;
if (ActivityCompat.checkSelfPermission(getApplicationContext(), Manifest.permission.BLUETOOTH_CONNECT) != PackageManager.PERMISSION_GRANTED) {
return;
}
mBluetoothGattServer.sendResponse(device, requestId, responseStatus, 0, null);
}
}
};
if (mBluetoothAdapter.isMultipleAdvertisementSupported()) {
stopAdvertising();
startAdvertising();
}
private void startAdvertising() {
if (isAdvertising) {
// Advertising is already in progress
return;
}
final byte[] serviceData = new byte[] { /* TODO アドバタイズ用のデータ作成 */ };
advertiser = mBluetoothAdapter.getBluetoothLeAdvertiser();
AdvertiseSettings settings = new AdvertiseSettings.Builder()
.setAdvertiseMode(AdvertiseSettings.ADVERTISE_MODE_BALANCED)
.setConnectable(true)
.setTimeout(0)
.setTxPowerLevel(AdvertiseSettings.ADVERTISE_TX_POWER_HIGH)
.build();
// Advertise Packetに入れる情報をセット(31bytes内で)
// Central側はScanでこの情報を取得する
final AdvertiseData data = new AdvertiseData.Builder()
.setIncludeDeviceName(false)
//.addServiceUuid(new ParcelUuid(SERVICE_UUID))
.addServiceData(new ParcelUuid(SERVICE_DATA_UUID), serviceData)
.build();
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.BLUETOOTH_ADVERTISE) != PackageManager.PERMISSION_GRANTED) {
return;
}
// スキャン応答用のデータ(Option)
byte[] scanRspData = "123456789012345678901234567".getBytes(StandardCharsets.UTF_8);
final AdvertiseData scanRsp = new AdvertiseData.Builder()
.setIncludeDeviceName(false)
.addServiceData(new ParcelUuid(SERVICE_DATA_UUID_FOR_SCAN_RSP), scanRspData)
.build();
isAdvertising = true;
//advertiser.startAdvertising(settings, data, advertiseCallback); // スキャン応答なし
advertiser.startAdvertising(settings, data, scanRsp, advertiseCallback);
}
private void stopAdvertising() {
Log.i(TAG, "stopAdvertising");
if (!isAdvertising || advertiser == null) {
return;
}
isAdvertising = false;
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.BLUETOOTH_ADVERTISE) == PackageManager.PERMISSION_GRANTED) {
advertiser.stopAdvertising(advertiseCallback);
}
}
private final AdvertiseCallback advertiseCallback = new AdvertiseCallback() {
@Override
public void onStartSuccess(AdvertiseSettings settingsInEffect) {}
@Override
public void onStartFailure(int errorCode) {}
};
final BluetoothManager bm = (BluetoothManager) context.getSystemService(Context.BLUETOOTH_SERVICE);
final BluetoothAdapter adapter = bm.getAdapter();
if (adapter != null) {
bluetoothLeScanner = adapter.getBluetoothLeScanner();
}
// スキャンフィルターの設定
final ParcelUuid sdUuid = ParcelUuid.fromString(SERVICE_DATA_UUID__STARTUP);
final ScanFilter filter = new ScanFilter.Builder()
//.setServiceUuid(ParcelUuid.fromString(SERVICE_UUID))
// Service DataのUUIDでフィルターをかける場合
.setServiceData(sdUuid, new byte[] {}, null)
.build();
final List<ScanFilter> filters = new ArrayList<>();
filters.add(filter);
// スキャン設定
final ScanSettings settings = new ScanSettings.Builder()
.setCallbackType(ScanSettings.CALLBACK_TYPE_ALL_MATCHES)
.setScanMode(ScanSettings.SCAN_MODE_BALANCED)
.setLegacy(true) // Bluetooth 4.2以前のAdvertiseだけをスキャン結果として通知する(基本)
.build();
// スキャン開始
if (!checkBlePermission()) return;
bluetoothLeScanner.startScan(filters, settings, scanCallback);
public boolean checkBlePermission() {
return ActivityCompat.checkSelfPermission(
context,
android.Manifest.permission.BLUETOOTH_CONNECT
) == PackageManager.PERMISSION_GRANTED;
}
private final ScanCallback scanCallback = new ScanCallback() {
@Override
public void onScanResult(int callbackType, ScanResult result) {
final BluetoothDevice device = result.getDevice();
// 接続済みデバイスかを確認
if (connectedDeviceAddress != null) {
return;
}
final ScanRecord record = result.getScanRecord();
if (record == null) return;
// アドバタイズの固有のデータを取得する
final byte[] serviceData = record.getServiceData(ADV_SERVICE_DATA_UUID);
// スキャン応答のデータ
final byte[] rspData = record.getServiceData(SCAN_RSP_SERVICE_DATA_UUID);
final boolean enableConnect;
if (serviceData != null) {
// 信号の強度
final int rssi = result.getRssi();
// TODO ServiceDataの情報を解析して、接続するか、否かを判断
enableConnect = true;
} else {
enableConnect = false;
}
if (!enableConnect) {
// 接続する必要があるない場合は、ここで中断
return;
}
if (!checkBlePermission()) return;
// まだ接続されていないので、このデバイスと接続
mBluetoothGatt = device.connectGatt(context, false, gattCallback);
connectedDeviceAddress = device.getAddress(); // デバイスのMACアドレスを保存
// スキャン中止
bluetoothLeScanner.stopScan(scanCallback);
}
};
private final BluetoothGattCallback gattCallback = new BluetoothGattCallback() {
@Override
public void onServiceChanged(@NonNull BluetoothGatt gatt) {
super.onServiceChanged(gatt);
}
@Override
public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
switch (newState) {
case BluetoothProfile.STATE_CONNECTED: {
if (!checkBlePermission()) {
return;
}
// スキャン中止
bluetoothLeScanner.stopScan(scanCallback);
gatt.requestMtu(512); // MTUを最大値で要求
try {
Thread.sleep(2000);
} catch (InterruptedException ignored) {}
// サービスを発見
gatt.discoverServices();
break;
}
case BluetoothProfile.STATE_DISCONNECTED: {
connectedDeviceAddress = null;
break;
}
default: break;
}
}
@Override
public void onServicesDiscovered(BluetoothGatt gatt, int status) {
if (status != BluetoothGatt.GATT_SUCCESS) {
return;
}
try {
Thread.sleep(500);
} catch (InterruptedException e) {
logger.e(e.toString());
}
final BluetoothGattService service = gatt.getService(serviceUuid);
if (service == null) return;
// Characteristic取得
final BluetoothGattCharacteristic characteristic = service.getCharacteristic(CHAR_UUID);
if (characteristic != null) {
// TODO ...
}
}
@Override
public void onCharacteristicRead(
@NonNull BluetoothGatt gatt,
@NonNull BluetoothGattCharacteristic characteristic,
@NonNull byte[] value,
int status
) {
// Peripheralからデータを受け取った場合
}
@Override
public void onCharacteristicWrite(
BluetoothGatt gatt,
BluetoothGattCharacteristic characteristic,
int status
) {
// Peripheralにデータを送信した後、Peripheralか応答がある場合
}
};