標籤:csdn mvn 結果 for hub create off scanf 建立
一.Android 低功耗藍芽(BLE)的API簡介
從Android 4.3(API 18)才支援低功耗藍芽(Bluetooth Low Energy, BLE)的核心功能,BLE藍芽協議是GATT協議, BLE相關類不多, 全都位於android.bluetooth包和android.bluetooth.le包的幾個類:android.bluetooth. .BluetoothGattService 包含多個Characteristic(屬性特徵值), 含有唯一的UUID作為標識 .BluetoothGattCharacteristic 包含單個值和多個Descriptor, 含有唯一的UUID作為標識 .BluetoothGattDescriptor 對Characteristic進行描述, 含有唯一的UUID作為標識 .BluetoothGatt 用戶端相關 .BluetoothGattCallback 用戶端串連回調 .BluetoothGattServer 服務端相關 .BluetoothGattServerCallback 服務端串連回調android.bluetooth.le. .AdvertiseCallback 服務端的廣播回調 .AdvertiseData 服務端的廣播資料 .AdvertiseSettings 服務端的廣播設定 .BluetoothLeAdvertiser 服務端的廣播 .BluetoothLeScanner 用戶端掃描相關(Android5.0新增) .ScanCallback 用戶端掃描回調 .ScanFilter 用戶端掃描過濾 .ScanRecord 用戶端掃描結果的廣播資料 .ScanResult 用戶端掃描結果 .ScanSettings 用戶端掃描設定 BLE裝置分為兩種裝置: 用戶端(也叫主機/中心裝置/Central), 服務端(也叫從機/外圍裝置/peripheral)用戶端的核心類是 BluetoothGatt服務端的核心類是 BluetoothGattServer 和 BluetoothLeAdvertiserBLE資料的核心類是 BluetoothGattCharacteristic 和 BluetoothGattDescriptor
二.低功耗藍芽(BLE)-手機同時作為BLE用戶端和BLE服務端,讀寫Characteristic資料
完整源碼: https://github.com/lifegh/Bluetooth
bt_client.png
bt_server.png
1.藍芽許可權
BLE許可權增加了BEL支援檢查,其它與上篇的經典藍芽相同,不再寫了(1).在manifest中添加許可權(2).在Activity中設定藍芽// 檢查是否支援BLE藍芽if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) { Util.toast(this, "本機不支援低功耗藍芽!"); finish(); return;}
2.BLE用戶端(也叫主機/中心裝置/Central)(1).掃描BLE裝置(不包含經典藍芽)
注意: BLE裝置地址是動態變化(每隔一段時間都會變化),而經典藍牙裝置是出廠就固定不變了!BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();// 下面使用Android5.0新增的掃描API,掃描返回的結果更友好,比如BLE廣播資料以前是byte[] scanRecord,而新API幫我們解析成ScanRecord類// 舊API是BluetoothAdapter.startLeScan(...)final BluetoothLeScanner bluetoothLeScanner = bluetoothAdapter.getBluetoothLeScanner();bluetoothLeScanner.startScan(mScanCallback);mHandler.postDelayed(new Runnable() { @Override public void run() { bluetoothLeScanner.stopScan(mScanCallback); //停止掃描 isScanning = false; }}, 3000);// 掃描結果Callbackprivate final ScanCallback mScanCallback = new ScanCallback() { @Override public void onScanResult(int callbackType, ScanResult result) {、 BluetoothDevice dev = result.getDevice() 擷取BLE裝置資訊 // result.getScanRecord() 擷取BLE廣播資料 }};
(2).建立串連
// 擷取掃描裝置,建立串連closeConn();BluetoothDevice dev = result.getDevice()mBluetoothGatt = dev.connectGatt(BleClientActivity.this, false, mBluetoothGattCallback); // 串連藍牙裝置// BLE中心裝置串連外圍裝置的數量有限(大概2~7個),在建立新串連之前必須釋放舊串連資源,否則容易出現串連錯誤133private void closeConn() { if (mBluetoothGatt != null) { mBluetoothGatt.disconnect(); mBluetoothGatt.close(); }} // 與服務端串連的Callbackpublic BluetoothGattCallback mBluetoothGattCallback = new BluetoothGattCallback() { @Override public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) { BluetoothDevice dev = gatt.getDevice(); Log.i(TAG, String.format("onConnectionStateChange:%s,%s,%s,%s", dev.getName(), dev.getAddress(), status, newState)); if (status == BluetoothGatt.GATT_SUCCESS && newState == BluetoothProfile.STATE_CONNECTED) { isConnected = true; gatt.discoverServices(); //啟動服務發現 } else { isConnected = false; closeConn(); } logTv(String.format(status == 0 ? (newState == 2 ? "與[%s]串連成功" : "與[%s]串連斷開") : ("與[%s]串連出錯,錯誤碼:" + status), dev)); } @Override public void onServicesDiscovered(BluetoothGatt gatt, int status) { Log.i(TAG, String.format("onServicesDiscovered:%s,%s,%s", gatt.getDevice().getName(), gatt.getDevice().getAddress(), status)); if (status == BluetoothGatt.GATT_SUCCESS) { //BLE服務發現成功 // 遍曆擷取BLE服務Services/Characteristics/Descriptors的全部UUID for (BluetoothGattService service : gatt.getServices()) { StringBuilder allUUIDs = new StringBuilder("UUIDs={nS=" + service.getUuid().toString()); for (BluetoothGattCharacteristic characteristic : service.getCharacteristics()) { allUUIDs.append(",nC=").append(characteristic.getUuid()); for (BluetoothGattDescriptor descriptor : characteristic.getDescriptors()) allUUIDs.append(",nD=").append(descriptor.getUuid()); } allUUIDs.append("}"); Log.i(TAG, "onServicesDiscovered:" + allUUIDs.toString()); logTv("探索服務" + allUUIDs); } } } @Override public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) { UUID uuid = characteristic.getUuid(); String valueStr = new String(characteristic.getValue()); Log.i(TAG, String.format("onCharacteristicRead:%s,%s,%s,%s,%s", gatt.getDevice().getName(), gatt.getDevice().getAddress(), uuid, valueStr, status)); logTv("讀取Characteristic[" + uuid + "]:n" + valueStr); } @Override public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) { UUID uuid = characteristic.getUuid(); String valueStr = new String(characteristic.getValue()); Log.i(TAG, String.format("onCharacteristicWrite:%s,%s,%s,%s,%s", gatt.getDevice().getName(), gatt.getDevice().getAddress(), uuid, valueStr, status)); logTv("寫入Characteristic[" + uuid + "]:n" + valueStr); } @Override public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) { UUID uuid = characteristic.getUuid(); String valueStr = new String(characteristic.getValue()); Log.i(TAG, String.format("onCharacteristicChanged:%s,%s,%s,%s", gatt.getDevice().getName(), gatt.getDevice().getAddress(), uuid, valueStr)); logTv("通知Characteristic[" + uuid + "]:n" + valueStr); } @Override public void onDescriptorRead(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) { UUID uuid = descriptor.getUuid(); String valueStr = Arrays.toString(descriptor.getValue()); Log.i(TAG, String.format("onDescriptorRead:%s,%s,%s,%s,%s", gatt.getDevice().getName(), gatt.getDevice().getAddress(), uuid, valueStr, status)); logTv("讀取Descriptor[" + uuid + "]:n" + valueStr); } @Override public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) { UUID uuid = descriptor.getUuid(); String valueStr = Arrays.toString(descriptor.getValue()); Log.i(TAG, String.format("onDescriptorWrite:%s,%s,%s,%s,%s", gatt.getDevice().getName(), gatt.getDevice().getAddress(), uuid, valueStr, status)); logTv("寫入Descriptor[" + uuid + "]:n" + valueStr); }};
(3).傳輸資料(讀寫CHARACTERISTIC和DESCRIPTOR)
注意: 1.每次讀寫資料最多20個位元組,如果超過,只能分包 2.連續頻繁讀寫資料容易失敗,讀寫操作間隔最好200ms以上,或等待上次回調完成後再進行下次讀寫操作!// 讀取資料成功會回調->onCharacteristicChanged()public void read(View view) { BluetoothGattService service = getGattService(BleServerActivity.UUID_SERVICE); if (service != null) { BluetoothGattCharacteristic characteristic = service.getCharacteristic(BleServerActivity.UUID_CHAR_READ_NOTIFY);//通過UUID擷取可讀的Characteristic mBluetoothGatt.readCharacteristic(characteristic); }}// 寫入資料成功會回調->onCharacteristicWrite()public void write(View view) { BluetoothGattService service = getGattService(BleServerActivity.UUID_SERVICE); if (service != null) { String text = mWriteET.getText().toString(); BluetoothGattCharacteristic characteristic = service.getCharacteristic(BleServerActivity.UUID_CHAR_WRITE);//通過UUID擷取可寫的Characteristic characteristic.setValue(text.getBytes()); //單次最多20個位元組 mBluetoothGatt.writeCharacteristic(characteristic); }}// 擷取Gatt服務private BluetoothGattService getGattService(UUID uuid) { BluetoothGattService service = mBluetoothGatt.getService(uuid); if (service == null) Util.toast(this, "沒有找到服務UUID=" + uuid); return service;}
(4).設定通知,即時監聽CHARACTERISTIC變化
// Characteristic變化會回調->onCharacteristicChanged()BluetoothGattService service = getGattService(BleServerActivity.UUID_SERVICE);if (service != null) { // 設定Characteristic通知 BluetoothGattCharacteristic characteristic = service.getCharacteristic(BleServerActivity.UUID_CHAR_READ_NOTIFY);//通過UUID擷取可通知的Characteristic mBluetoothGatt.setCharacteristicNotification(characteristic, true); // 向Characteristic的Descriptor屬性寫入通知開關,使藍牙裝置主動向手機發送資料 BluetoothGattDescriptor descriptor = characteristic.getDescriptor(BleServerActivity.UUID_DESC_NOTITY); // descriptor.setValue(BluetoothGattDescriptor.ENABLE_INDICATION_VALUE);//和通知類似,但服務端不主動發資料,只指示用戶端讀取資料 descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE); mBluetoothGatt.writeDescriptor(descriptor);}
3.BLE服務端(也叫從機/外圍裝置/peripheral)
public static final UUID UUID_SERVICE = UUID.fromString("10000000-0000-0000-0000-000000000000"); //自訂UUIDpublic static final UUID UUID_CHAR_READ_NOTIFY = UUID.fromString("11000000-0000-0000-0000-000000000000");public static final UUID UUID_DESC_NOTITY = UUID.fromString("11100000-0000-0000-0000-000000000000");public static final UUID UUID_CHAR_WRITE = UUID.fromString("12000000-0000-0000-0000-000000000000");private BluetoothLeAdvertiser mBluetoothLeAdvertiser; // BLE廣播private BluetoothGattServer mBluetoothGattServer; // BLE服務端@Overrideprotected void onCreate(Bundle savedInstanceState) { ...... BluetoothManager bluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE); // BluetoothAdapter bluetoothAdapter = bluetoothManager.getAdapter(); BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); // ============啟動BLE藍芽廣播(廣告) ================================================================================= //廣播設定(必須) AdvertiseSettings settings = new AdvertiseSettings.Builder() .setAdvertiseMode(AdvertiseSettings.ADVERTISE_MODE_LOW_LATENCY) //廣播模式: 低功耗,平衡,低延遲 .setTxPowerLevel(AdvertiseSettings.ADVERTISE_TX_POWER_HIGH) //發射功率層級: 極低,低,中,高 .setConnectable(true) //能否串連,廣播分為可串連廣播和不可串連廣播 .build(); //廣播資料(必須,廣播啟動就會發送) AdvertiseData advertiseData = new AdvertiseData.Builder() .setIncludeDeviceName(true) //包含藍芽名稱 .setIncludeTxPowerLevel(true) //包含發射功率層級 .addManufacturerData(1, new byte[]{23, 33}) //裝置廠商資料,自訂 .build(); //掃描響應資料(可選,當用戶端掃描時才發送) AdvertiseData scanResponse = new AdvertiseData.Builder() .addManufacturerData(2, new byte[]{66, 66}) //裝置廠商資料,自訂 .addServiceUuid(new ParcelUuid(UUID_SERVICE)) //服務UUID // .addServiceData(new ParcelUuid(UUID_SERVICE), new byte[]{2}) //服務資料,自訂 .build(); mBluetoothLeAdvertiser = bluetoothAdapter.getBluetoothLeAdvertiser(); mBluetoothLeAdvertiser.startAdvertising(settings, advertiseData, scanResponse, mAdvertiseCallback); // 注意:必須要開啟可串連的BLE廣播,其它裝置才能發現並串連BLE服務端! // =============啟動BLE藍芽服務端===================================================================================== BluetoothGattService service = new BluetoothGattService(UUID_SERVICE, BluetoothGattService.SERVICE_TYPE_PRIMARY); //添加可讀+通知characteristic BluetoothGattCharacteristic characteristicRead = new BluetoothGattCharacteristic(UUID_CHAR_READ_NOTIFY, BluetoothGattCharacteristic.PROPERTY_READ | BluetoothGattCharacteristic.PROPERTY_NOTIFY, BluetoothGattCharacteristic.PERMISSION_READ); characteristicRead.addDescriptor(new BluetoothGattDescriptor(UUID_DESC_NOTITY, BluetoothGattCharacteristic.PERMISSION_WRITE)); service.addCharacteristic(characteristicRead); //添加可寫characteristic BluetoothGattCharacteristic characteristicWrite = new BluetoothGattCharacteristic(UUID_CHAR_WRITE, BluetoothGattCharacteristic.PROPERTY_WRITE, BluetoothGattCharacteristic.PERMISSION_WRITE); service.addCharacteristic(characteristicWrite); if (bluetoothManager != null) mBluetoothGattServer = bluetoothManager.openGattServer(this, mBluetoothGattServerCallback); mBluetoothGattServer.addService(service);}// BLE廣播Callbackprivate AdvertiseCallback mAdvertiseCallback = new AdvertiseCallback() { @Override public void onStartSuccess(AdvertiseSettings settingsInEffect) { logTv("BLE廣播開啟成功"); } @Override public void onStartFailure(int errorCode) { logTv("BLE廣播開啟失敗,錯誤碼:" + errorCode); }};// BLE服務端Callbackprivate BluetoothGattServerCallback mBluetoothGattServerCallback = new BluetoothGattServerCallback() { @Override public void onConnectionStateChange(BluetoothDevice device, int status, int newState) { Log.i(TAG, String.format("onConnectionStateChange:%s,%s,%s,%s", device.getName(), device.getAddress(), status, newState)); logTv(String.format(status == 0 ? (newState == 2 ? "與[%s]串連成功" : "與[%s]串連斷開") : ("與[%s]串連出錯,錯誤碼:" + status), device)); } @Override public void onServiceAdded(int status, BluetoothGattService service) { Log.i(TAG, String.format("onServiceAdded:%s,%s", status, service.getUuid())); logTv(String.format(status == 0 ? "添加服務[%s]成功" : "添加服務[%s]失敗,錯誤碼:" + status, service.getUuid())); } @Override public void onCharacteristicReadRequest(BluetoothDevice device, int requestId, int offset, BluetoothGattCharacteristic characteristic) { Log.i(TAG, String.format("onCharacteristicReadRequest:%s,%s,%s,%s,%s", device.getName(), device.getAddress(), requestId, offset, characteristic.getUuid())); String response = "CHAR_" + (int) (Math.random() * 100); //類比資料 mBluetoothGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, offset, response.getBytes());// 響應用戶端 logTv("用戶端讀取Characteristic[" + characteristic.getUuid() + "]:n" + response); } @Override public void onCharacteristicWriteRequest(BluetoothDevice device, int requestId, BluetoothGattCharacteristic characteristic, boolean preparedWrite, boolean responseNeeded, int offset, byte[] requestBytes) { // 擷取用戶端發過來的資料 String requestStr = new String(requestBytes); Log.i(TAG, String.format("onCharacteristicWriteRequest:%s,%s,%s,%s,%s,%s,%s,%s", device.getName(), device.getAddress(), requestId, characteristic.getUuid(), preparedWrite, responseNeeded, offset, requestStr)); mBluetoothGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, offset, requestBytes);// 響應用戶端 logTv("用戶端寫入Characteristic[" + characteristic.getUuid() + "]:n" + requestStr); } @Override public void onDescriptorReadRequest(BluetoothDevice device, int requestId, int offset, BluetoothGattDescriptor descriptor) { Log.i(TAG, String.format("onDescriptorReadRequest:%s,%s,%s,%s,%s", device.getName(), device.getAddress(), requestId, offset, descriptor.getUuid())); String response = "DESC_" + (int) (Math.random() * 100); //類比資料 mBluetoothGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, offset, response.getBytes()); // 響應用戶端 logTv("用戶端讀取Descriptor[" + descriptor.getUuid() + "]:n" + response); } @Override public void onDescriptorWriteRequest(final BluetoothDevice device, int requestId, BluetoothGattDescriptor descriptor, boolean preparedWrite, boolean responseNeeded, int offset, byte[] value) { // 擷取用戶端發過來的資料 String valueStr = Arrays.toString(value); Log.i(TAG, String.format("onDescriptorWriteRequest:%s,%s,%s,%s,%s,%s,%s,%s", device.getName(), device.getAddress(), requestId, descriptor.getUuid(), preparedWrite, responseNeeded, offset, valueStr)); mBluetoothGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, offset, value);// 響應用戶端 logTv("用戶端寫入Descriptor[" + descriptor.getUuid() + "]:n" + valueStr); // 簡單類比通知用戶端Characteristic變化 if (Arrays.toString(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE).equals(valueStr)) { //是否開啟通知 final BluetoothGattCharacteristic characteristic = descriptor.getCharacteristic(); new Thread(new Runnable() { @Override public void run() { for (int i = 0; i < 5; i++) { SystemClock.sleep(3000); String response = "CHAR_" + (int) (Math.random() * 100); //類比資料 characteristic.setValue(response); mBluetoothGattServer.notifyCharacteristicChanged(device, characteristic, false); logTv("通知用戶端改變Characteristic[" + characteristic.getUuid() + "]:n" + response); } } }).start(); } } @Override public void onExecuteWrite(BluetoothDevice device, int requestId, boolean execute) { Log.i(TAG, String.format("onExecuteWrite:%s,%s,%s,%s", device.getName(), device.getAddress(), requestId, execute)); } @Override public void onNotificationSent(BluetoothDevice device, int status) { Log.i(TAG, String.format("onNotificationSent:%s,%s,%s", device.getName(), device.getAddress(), status)); } @Override public void onMtuChanged(BluetoothDevice device, int mtu) { Log.i(TAG, String.format("onMtuChanged:%s,%s,%s", device.getName(), device.getAddress(), mtu)); }};
簡書: https://www.jianshu.com/p/8ac31a5070d4
CSDN: 80643906
GitHub部落格: http://lioil.win/2018/06/10/Android-BLE.html
Coding部落格: http://c.lioil.win/2018/06/10/Android-BLE.html
Android-低功耗藍芽(BLE)-用戶端(主機/中心裝置)和服務端(從機/外圍裝置)