android(os 5.0以上)でcentral側の実装をして見たいと思います。
centralとはclientに該当し、peripheral(server)に接続して、
peripheralに接続された他の端末のデータをperipheralから受信したり、
peripheralに自信のデータを送信する役割を担います。
機能としては、ios・android側のperipheral端末に接続し、
データのやりとりをできるようにしたいと思います。
まずは、BluetoothLEを使うための部品をOnCreateでinstance化します。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
private lateinit var bleGatt: BluetoothGatt private lateinit var bleScanner: BluetoothLeScanner private lateinit var bleCharacteristic: BluetoothGattCharacteristic private lateinit var bleAdapter: BluetoothAdapter private lateinit var bleManager: BluetoothManager override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_central) // init bleManager = getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager bleAdapter = bleManager.getAdapter() } |
続いて、advertiseと呼ばれるperipheralが発信している信号をcentral側が検索するコードです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
fun scanNewDevice() { // 1.bluetoothが有効か if (!bleAdapter.isEnabled) { Toast.makeText(this, "Please enable Bluetooth", Toast.LENGTH_SHORT) } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { startScanByBleScanner() } else { bleAdapter.startLeScan(BluetoothAdapter.LeScanCallback { device, rssi, scanRecord -> // スキャン中に見つかったデバイスに接続を試みる.第三引数には接続後に呼ばれるBluetoothGattCallbackを指定する. Log.d(TAG, "find device") bleGatt = device.connectGatt(getApplicationContext(), false, gattCallBack) }) } } |
1.端末設定でbluetoothが有効になっているかどうかを確認してください。
2.BluetoothLeを使うためのScannerはos5.0以上でないと対応していません。
peripheral端末を探索する
探索端末のフィルタリング
peripehral端末はScanFilterクラスを使うことでフィルターをかけてscanをすることができます。
ここでは、ServiceUuidに対してフィルターをかけています。
ScanModeの設定
Scanする際に、処理にどれだけパワーを割り当てるのかをScanSettingsクラスを使用して設定する必要があります。
ここでは、一般的にSCAN_MODE_BALANCEDを割り当てれば問題ないようです。
deviceのscanを行う
探索を行うには、BluetoothLeScannerクラスのstartScan関数を呼びます。
引数として、接続結果を受け取るためのscanCallBack関数などをセットします。
os6.o以上の対応
以下にあるようにstartScan関数を使うためには、
ACCESS_COARSE_LOCATIONまたはACCESS_FINE_LOCATION
の許可が必要になります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
/** * Start Bluetooth LE scan. The scan results will be delivered through {@code callback}. * <p> * An app must hold * {@link android.Manifest.permission#ACCESS_COARSE_LOCATION ACCESS_COARSE_LOCATION} or * {@link android.Manifest.permission#ACCESS_FINE_LOCATION ACCESS_FINE_LOCATION} permission * in order to get results. * * @param filters {@link ScanFilter}s for finding exact BLE devices. * @param settings Settings for the scan. * @param callback Callback used to deliver scan results. * @throws IllegalArgumentException If {@code settings} or {@code callback} is null. */ @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN) public void startScan(List<ScanFilter> filters, ScanSettings settings, final ScanCallback callback) { startScan(filters, settings, null, callback, /*callbackIntent=*/ null, null); } |
以上をふまえてのサンプルコードです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
/** * scan by BLE */ @TargetApi(Build.VERSION_CODES.LOLLIPOP) fun startScanByBleScanner() { bleScanner = bleAdapter.getBluetoothLeScanner() // フィルターするuuid val parcelUuid = ParcelUuid.fromString(getString(R.string.uuid_service)) // uuidにフィルターをかける val scanFilter = ScanFilter.Builder().setServiceUuid(parcelUuid).build() val scanFilters = ArrayList<ScanFilter>() scanFilters.add(scanFilter) // setting val settings = ScanSettings.Builder().setScanMode(ScanSettings.SCAN_MODE_BALANCED).build() // デバイスの検出. bleScanner.startScan(scanFilters, settings, scanCallback) // set text state.text = "scanning peripheral" } |
scan結果を踏まえてperipheral deviceに接続する
scanCallBack関数のonScanResult関数に探索した結果が返却されます。
今回はフィルタをかけたので、引数callbackTypeにフィルタ結果が返ってきます。
定数は公式サイトを参照してください。
peripheral deviceに接続する
ScanResultクラス内で接続されたBluetoothDeviceクラスを参照することができるので、
connectGatt関数を使用してperiperalデバイスに接続を試みます。
返り値にBluetoothGattクラスが返却されるので、これを受け取り、
stopScan関数でscanを停止します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
/** * BluetoothLe scan callback */ private val scanCallback = object : ScanCallback() { override fun onScanResult(callbackType: Int, result: ScanResult) { if (callbackType == CALLBACK_TYPE_ALL_MATCHES) { super.onScanResult(callbackType, result) // 18/2/20修正:最初にスキャンを止める bleScanner.stopScan(this) val bluetoothDevice = result.device // try to connect device bleGatt = bluetoothDevice.connectGatt(applicationContext, false, gattCallBack) } } override fun onScanFailed(intErrorCode: Int) { Log.d(TAG, "onScanFailed ErrorCode = " + intErrorCode) super.onScanFailed(intErrorCode) } } |
BluetoothGattCallBack関数
BluetoothGattCallBack関数にpeiephralとの接続状況などが返ってきます。
接続状態を知らせるonConnectionStateChange関数内で、
データの送信量を増やすためにrequestMtu関数を呼びます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
override fun onConnectionStateChange(gatt: BluetoothGatt, status: Int, newState: Int) { // 接続状況が変化したら実行. if (newState == BluetoothProfile.STATE_CONNECTED) { if (gatt.requestMtu(512)) { Log.d(TAG, "Requested MTU successfully") } else { Log.d(TAG, "Failed to request MTU") } } else if (newState == BluetoothProfile.STATE_DISCONNECTED) { Log.d(TAG, "call onConnectionStateChange STATE_DISCONNECTED") // 接続が切れたらGATTを空にする. if (bleGatt != null) { bleGatt.close() } } else if (newState == BluetoothProfile.STATE_CONNECTING) { Log.d(TAG, "call onConnectionStateChange STATE_CONNECTING") } else if (newState == BluetoothProfile.STATE_DISCONNECTING) { Log.d(TAG, "call onConnectionStateChange STATE_DISCONNECTING") } } |
requestMtuの結果もBluetoothGattCallBack関数として定義されています。
1 2 3 4 5 6 7 8 9 10 11 |
override fun onMtuChanged(gatt: BluetoothGatt?, mtu: Int, status: Int) { Log.d(TAG, "onMtuChanged") // discover services if (gatt != null) { if (gatt.discoverServices()) { Log.d(TAG, "Started discovering services") } else { Log.d(TAG, "Failed to start discovering services") } } } |
接続データ量の拡張に成功したら、discoverServices関数を呼び、
serviceを探索します。
この結果はonServicesDiscovered関数としてBluetoothGattCallBack内に定義されています。
onServiecesDiscovered関数内で、
peripheral側でサービスなどに指定したUnique Idがマッチしているのかチェックし、
peripehral側からのメッセージを受け取れるようにNotificationをリクエストします。
その際に、BluetoothGattDesciptorを使用します。
BluetoothGattDesciptorクラス
Desciptorという単語は知らなかったのですが、日本語でいうと記述子にあたり、
記述子はファイルの構造や内容の要約、ファイルの属性を意味するようです。
公式サイトによると、GattCharacteristicの付加情報を管理するなどと書かれていました。
characteristicからBluetoothGattDesciptorを参照する
参照する際のidは固有値なので、注意してください。
1 |
val bleDescriptor = bleCharacteristic.getDescriptor(UUID.fromString(getString(R.string.uuid_characteristic_config))) |
これらを踏まえての全体ソースです。
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 |
override fun onServicesDiscovered(gatt: BluetoothGatt, status: Int) { // Serviceが見つかったら実行. if (status == BluetoothGatt.GATT_SUCCESS) { Log.d(TAG, "call onServicesDiscovered() GATT_SUCCESS") // UUIDが同じかどうかを確認する. val bleService = gatt.getService(UUID.fromString(getString(R.string.uuid_service))) if (bleService != null) { // 指定したUUIDを持つCharacteristicを確認する. bleCharacteristic = bleService.getCharacteristic(UUID.fromString(getString(R.string.uuid_characteristic))) if (bleCharacteristic != null) { // Service, CharacteristicのUUIDが同じならBluetoothGattを更新する. bleGatt = gatt // キャラクタリスティックが見つかったら、Notificationをリクエスト. bleGatt.setCharacteristicNotification(bleCharacteristic, true) // Characteristic の Notificationを有効化する. val bleDescriptor = bleCharacteristic.getDescriptor( UUID.fromString(getString(R.string.uuid_characteristic_config))) bleDescriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE) // Write the value of a given descriptor to the associated remote device. bleGatt.writeDescriptor(bleDescriptor) // 接続完了 isConnect = true } } } } |
peripheral側にデータを送る
onServicesDiscovered関数内でwriteDecriptor関数を呼んでいますが、
そのcallback関数でperipheral端末にデータを送信するコードを書いています。
こうすることで、初回に送るデータを区別することができます。
1 2 3 4 5 6 7 |
override fun onDescriptorWrite(gatt: BluetoothGatt, descriptor: BluetoothGattDescriptor, status: Int) { super.onDescriptorWrite(gatt, descriptor, status) Log.d(TAG, "call onDescriptorWrite") val message = input_message.text.toString() bleCharacteristic.setValue(message) bleGatt.writeCharacteristic(bleCharacteristic) } |
送信方法は、BluetoothGattCharacteristicのsetValue関数で値をセットし、
BluetoothGattクラスのwriteCharcteristic関数でデータを書き込むことで、
Peripheral側にCharacteristicの更新を伝えます。
Peripheral側からデータを受け取る
onCharacteristicChanged関数内でperipheral側のデータを受け取ることができます。
1 2 3 4 5 6 7 8 9 10 |
override fun onCharacteristicChanged(gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic) { Log.d(TAG, "call onCharacteristicChanged") // check characteristic uuid if (getString(R.string.uuid_characteristic).equals(characteristic.uuid.toString())) { val peripheralValue = characteristic.getStringValue(0) runOnUiThread({ message_text.text = peripheralValue }) } } |
参考
ベースはこちらを参照させていただき、あとはdeveloperサイトやappleのCoreBluetoothのドキュメントを参考にしました。
概念を知るには、CoreBluetoothのドキュメントが参考になりました。
ソースコード
全体のソースはgitにあげました。
iosのperipheral側とでデータのやり取りをすることができます。
iosはこちら。
androidのperipheral側の記事はこちら