首先,進行一下科普:
1.BLE(Bluetooth Low Energy),藍牙4.0核心profile,主要特點是快速搜索,快速連接,超低功耗保持連接和數據傳輸,缺點:數據傳輸速率低,由於其具有低功耗特點,故而經常用在可穿戴設備之中。Android4.3才開始支持BLE api。
2.關於BLE數據傳輸:
a.profile可以理解為一種規范,一個標准的通信協議,其存在於手機中,藍牙組織規定了一些標准的profile:HID OVER GATT ,防丟器等,每個profile中包含了多個service。
b.service 可以理解為一個服務,在BLE從機中有多個服務,電量信息,系統服務信息等,每一個service中包含了多個characteristic特征值,每一個具體的characteristic特征值才是BLE通信的主題。
舉個例子吧:從機當前的電量是80%,從機會借由電量的characteristic特征值將這個信息儲存於從機的profile中,主機可以通過該characteristic來讀取80%這個數據。
c.characteristic特征值:BLE主機從機通信均是通過characteristic進行,可以將其理解為一個標簽,通過該標簽可以讀取或寫入相關信息。
d. UUID(統一標識碼):service和characteristic均需要這個唯一的UUID進行標識
/****************************************************************************************/
科普結束,首先上效果圖:
這個就是實現后的效果,我用的BLE模塊是:MicrodunioBT4.0
/*********************************************************分隔符********************************************************************************/
BLE設備和Android應用進行通信,首先要做的就是讓Android應用找到BLE設備(代碼分析部分,只對關鍵點進行注釋)
package com.TANK.temperature.BT; import java.util.List; import com.TANK.temperature.R; import com.TANK.temperature.BT.BluetoothLeClass.OnDataAvailableListener; import com.TANK.temperature.BT.BluetoothLeClass.OnServiceDiscoverListener; import android.app.Activity; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothGatt; import android.bluetooth.BluetoothGattCharacteristic; import android.bluetooth.BluetoothGattDescriptor; import android.bluetooth.BluetoothGattService; import android.bluetooth.BluetoothManager; import android.content.Context; import android.content.pm.PackageManager; import android.graphics.Typeface; import android.net.wifi.WifiConfiguration.Status; import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.util.Log; import android.widget.TextView; import android.widget.Toast; public class DeviceScanActivity extends Activity { private final static String TAG = DeviceScanActivity.class.getSimpleName(); private final static String UUID_KEY_DATA = "0000fff6-0000-1000-8000-00805f9b34fb"; private Handler mHandler; private static final long SCAN_PERIOD = 10000; /** 搜索BLE終端 */ private BluetoothAdapter mBluetoothAdapter; private BluetoothLeClass mBLE; private boolean mScanning; private BluetoothDevice BTtoLink = null; private String BTtoFind = "Microduino"; private TextView BT_find, BT_info, BT_link;//三個textview分別表示:設備是否找到,BLE模塊傳輸的信息,連接狀態 private String S_BT_info = "null", info = "111"; @Override protected void onCreate(Bundle savedInstanceState) { // TODO Auto-generated method stub super.onCreate(savedInstanceState); setContentView(R.layout.temperature); mHandler = new Handler(); BT_find = (TextView) findViewById(R.id.BT_find); BT_info = (TextView) findViewById(R.id.BT_info); BT_link = (TextView) findViewById(R.id.BT_link); Typeface typeface = Typeface.createFromAsset(getAssets(), "fonts/maozedong.ttf");//設置顯示字體 BT_find.setTypeface(typeface); BT_info.setTypeface(typeface); BT_link.setTypeface(typeface); BT_find.setText("查找中"); BT_info.setText("null"); BT_link.setText("未連接"); if (!getPackageManager().hasSystemFeature( //判斷主機是否支BLE牙設備 PackageManager.FEATURE_BLUETOOTH_LE)) { Toast.makeText(this, "BLE is not supported", Toast.LENGTH_SHORT) .show(); finish(); } final BluetoothManager bluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);//獲得Android設備中的bluetoothmanager mBluetoothAdapter = bluetoothManager.getAdapter();//獲得bluetoothadapter if (mBluetoothAdapter == null) { Toast.makeText(this, "Bluetooth not supported", Toast.LENGTH_SHORT) .show(); finish(); return; } mBluetoothAdapter.enable(); //強制使能Bluetoothadapter,打開Android設備藍牙 mBLE = new BluetoothLeClass(this); //BLuetoothLeClass類 if (!mBLE.initialize()) { Log.e(TAG, "Unable to initialize Bluetooth"); finish(); } // 發現BLE終端的Service時回調 mBLE.setOnServiceDiscoverListener(mOnServiceDiscover); // 收到BLE終端數據交互的事件 mBLE.setOnDataAvailableListener(mOnDataAvailable); } @Override protected void onResume() { // TODO Auto-generated method stub super.onResume(); scanLeDevice(true);//搜索BLE設備 } @Override protected void onPause() { // TODO Auto-generated method stub super.onPause(); scanLeDevice(false); mBLE.disconnect(); } @Override protected void onStop() { // TODO Auto-generated method stub super.onStop(); mBLE.close(); } private void scanLeDevice(final boolean enable) { // TODO Auto-generated method stub if (enable) { mHandler.postDelayed(new Runnable() { @Override public void run() { // TODO Auto-generated method stub mScanning = false; mBluetoothAdapter.stopLeScan(mLeScanCallback); } }, SCAN_PERIOD);//在搜索時間內,關閉搜索標志,不對搜索回調函數進行響應 mScanning = true; mBluetoothAdapter.startLeScan(mLeScanCallback); } else { mScanning = false; mBluetoothAdapter.stopLeScan(mLeScanCallback); } } /** * 搜索到BLE終端服務的事件 */ private BluetoothLeClass.OnServiceDiscoverListener mOnServiceDiscover = new OnServiceDiscoverListener() { @Override public void onServiceDiscover(BluetoothGatt gatt) { displayGattServices(mBLE.getSupportedGattService()); } }; /** * 收到BLE終端數據交互的事件 */ private BluetoothLeClass.OnDataAvailableListener mOnDataAvailable = new OnDataAvailableListener() { /** * BLE終端數據寫入的事件 */ @Override public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) { if (status == BluetoothGatt.GATT_SUCCESS) Log.e(TAG, "onCharRead " + gatt.getDevice().getName() + " read " + characteristic.getUuid().toString() + " -> " + Utils.bytesToHexString(characteristic .getValue())); } /** * 對BLE終端讀取數據 */ @Override public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) { info = new String(characteristic.getValue());//對得到的byte數組進行解碼,構造新的string Log.e(TAG, "onCharWrite " + gatt.getDevice().getName() + " write " + characteristic.getUuid().toString() + " -> " + info); if (!S_BT_info.equals(info)) {//判斷讀取的數據是否發生變化,如果變化,更新UI DeviceScanActivity.this.runOnUiThread(new Runnable() { @Override public void run() { // TODO Auto-generated method stub BT_link.setText("已連接"); StringBuilder sb = new StringBuilder();//詳情參見:http://blog.csdn.net/rmn190/article/details/1492013 sb.append(info); sb.append("度"); BT_info.setText(sb.toString()); sb = null; } }); } } }; private BluetoothAdapter.LeScanCallback mLeScanCallback = new BluetoothAdapter.LeScanCallback() {//搜索回調函數: String BT_name = null; @Override public void onLeScan(final BluetoothDevice device, int rssi, byte[] scanRecord) { // TODO Auto-generated method stub runOnUiThread(new Runnable() { @Override public void run() { // TODO Auto-generated method stub BT_name = device.getName(); if (BT_name.equals(BTtoFind)) { //如果是要找的設備,更新UI上信息,設置搜索標志,停止響應搜索回調函數,連接BLE設備 /** 連接事件 */ BT_find.setText("已找到設備!"); mScanning = false; mBluetoothAdapter.stopLeScan(mLeScanCallback); mBLE.connect(device.getAddress()); } } }); } }; private void displayGattServices(BluetoothGattService bluetoothGattService) { if (bluetoothGattService == null) return; // -----Service的字段信息-----// int type = bluetoothGattService.getType(); Log.e(TAG, "-->service type:" + Utils.getServiceType(type)); Log.e(TAG, "-->includedServices size:" + bluetoothGattService.getIncludedServices().size()); Log.e(TAG, "-->service uuid:" + bluetoothGattService.getUuid()); // -----Characteristics的字段信息-----// List<BluetoothGattCharacteristic> gattCharacteristics = bluetoothGattService .getCharacteristics(); for (final BluetoothGattCharacteristic gattCharacteristic : gattCharacteristics) { Log.e(TAG, "---->char uuid:" + gattCharacteristic.getUuid()); int permission = gattCharacteristic.getPermissions(); Log.e(TAG, "---->char permission:" + Utils.getCharPermission(permission)); int property = gattCharacteristic.getProperties(); Log.e(TAG, "---->char property:" + Utils.getCharPropertie(property)); byte[] data = gattCharacteristic.getValue(); if (data != null && data.length > 0) { Log.e(TAG, "---->char value:" + new String(data)); } // UUID_KEY_DATA是可以跟藍牙模塊串口通信的Characteristic if (gattCharacteristic.getUuid().toString().equals(UUID_KEY_DATA)) { // 測試讀取當前Characteristic數據,會觸發mOnDataAvailable.onCharacteristicRead() mHandler.postDelayed(new Runnable() { @Override public void run() { mBLE.readCharacteristic(gattCharacteristic); } }, 500); // 接受Characteristic被寫的通知,收到藍牙模塊的數據后會觸發mOnDataAvailable.onCharacteristicWrite() mBLE.setCharacteristicNotification(gattCharacteristic, true); // 設置數據內容 gattCharacteristic.setValue("send data->"); // 往藍牙模塊寫入數據 mBLE.writeCharacteristic(gattCharacteristic); } // -----Descriptors的字段信息-----// List<BluetoothGattDescriptor> gattDescriptors = gattCharacteristic .getDescriptors(); for (BluetoothGattDescriptor gattDescriptor : gattDescriptors) { Log.e(TAG, "-------->desc uuid:" + gattDescriptor.getUuid()); int descPermission = gattDescriptor.getPermissions(); Log.e(TAG, "-------->desc permission:" + Utils.getDescPermission(descPermission)); byte[] desData = gattDescriptor.getValue(); if (desData != null && desData.length > 0) { Log.e(TAG, "-------->desc value:" + new String(desData)); } } } } }
package com.TANK.temperature.BT; import java.util.UUID; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothGatt; import android.bluetooth.BluetoothGattCallback; import android.bluetooth.BluetoothGattCharacteristic; import android.bluetooth.BluetoothGattService; import android.bluetooth.BluetoothManager; import android.bluetooth.BluetoothProfile; import android.content.Context; import android.util.Log; public class BluetoothLeClass{ private final static String TAG = BluetoothLeClass.class.getSimpleName(); private BluetoothManager mBluetoothManager; private BluetoothAdapter mBluetoothAdapter; private String mBluetoothDeviceAddress; private BluetoothGatt mBluetoothGatt; public interface OnConnectListener { public void onConnect(BluetoothGatt gatt); } public interface OnDisconnectListener { public void onDisconnect(BluetoothGatt gatt); } public interface OnServiceDiscoverListener { public void onServiceDiscover(BluetoothGatt gatt); } public interface OnDataAvailableListener { public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status); public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic); } private OnConnectListener mOnConnectListener; private OnDisconnectListener mOnDisconnectListener; private OnServiceDiscoverListener mOnServiceDiscoverListener; private OnDataAvailableListener mOnDataAvailableListener; private Context mContext; public void setOnConnectListener(OnConnectListener l){ mOnConnectListener = l; } public void setOnDisconnectListener(OnDisconnectListener l){ mOnDisconnectListener = l; } public void setOnServiceDiscoverListener(OnServiceDiscoverListener l){ mOnServiceDiscoverListener = l; } public void setOnDataAvailableListener(OnDataAvailableListener l){ mOnDataAvailableListener = l; } public BluetoothLeClass(Context c){ mContext = c; } // Implements callback methods for GATT events that the app cares about. For example, // connection change and services discovered. private final BluetoothGattCallback mGattCallback = new BluetoothGattCallback() { @Override public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) { if (newState == BluetoothProfile.STATE_CONNECTED) { if(mOnConnectListener!=null) mOnConnectListener.onConnect(gatt); Log.i(TAG, "Connected to GATT server."); // Attempts to discover services after successful connection. Log.i(TAG, "Attempting to start service discovery:" + mBluetoothGatt.discoverServices()); } else if (newState == BluetoothProfile.STATE_DISCONNECTED) { if(mOnDisconnectListener!=null) mOnDisconnectListener.onDisconnect(gatt); Log.i(TAG, "Disconnected from GATT server."); } } @Override public void onServicesDiscovered(BluetoothGatt gatt, int status) { if (status == BluetoothGatt.GATT_SUCCESS && mOnServiceDiscoverListener!=null) { mOnServiceDiscoverListener.onServiceDiscover(gatt); } else { Log.w(TAG, "onServicesDiscovered received: " + status); } } @Override public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) { if (mOnDataAvailableListener!=null) mOnDataAvailableListener.onCharacteristicRead(gatt, characteristic, status); } @Override public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) { if (mOnDataAvailableListener!=null) mOnDataAvailableListener.onCharacteristicWrite(gatt, characteristic); } }; /** * Initializes a reference to the local Bluetooth adapter. * * @return Return true if the initialization is successful. */ public boolean initialize() { // For API level 18 and above, get a reference to BluetoothAdapter through // BluetoothManager. if (mBluetoothManager == null) { mBluetoothManager = (BluetoothManager) mContext.getSystemService(Context.BLUETOOTH_SERVICE); if (mBluetoothManager == null) { Log.e(TAG, "Unable to initialize BluetoothManager."); return false; } } mBluetoothAdapter = mBluetoothManager.getAdapter(); if (mBluetoothAdapter == null) { Log.e(TAG, "Unable to obtain a BluetoothAdapter."); return false; } return true; } /** * Connects to the GATT server hosted on the Bluetooth LE device. * * @param address The device address of the destination device. * * @return Return true if the connection is initiated successfully. The connection result * is reported asynchronously through the * {@code BluetoothGattCallback#onConnectionStateChange(android.bluetooth.BluetoothGatt, int, int)} * callback. */ public boolean connect(final String address) { if (mBluetoothAdapter == null || address == null) { Log.w(TAG, "BluetoothAdapter not initialized or unspecified address."); return false; } // Previously connected device. Try to reconnect. if (mBluetoothDeviceAddress != null && address.equals(mBluetoothDeviceAddress) && mBluetoothGatt != null) { Log.d(TAG, "Trying to use an existing mBluetoothGatt for connection."); if (mBluetoothGatt.connect()) { return true; } else { return false; } } final BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(address); if (device == null) { Log.w(TAG, "Device not found. Unable to connect."); return false; } // We want to directly connect to the device, so we are setting the autoConnect // parameter to false. mBluetoothGatt = device.connectGatt(mContext, false, mGattCallback); Log.d(TAG, "Trying to create a new connection."); mBluetoothDeviceAddress = address; return true; } /** * Disconnects an existing connection or cancel a pending connection. The disconnection result * is reported asynchronously through the * {@code BluetoothGattCallback#onConnectionStateChange(android.bluetooth.BluetoothGatt, int, int)} * callback. */ public void disconnect() { if (mBluetoothAdapter == null || mBluetoothGatt == null) { Log.w(TAG, "BluetoothAdapter not initialized"); return; } mBluetoothGatt.disconnect(); } /** * After using a given BLE device, the app must call this method to ensure resources are * released properly. */ public void close() { if (mBluetoothGatt == null) { return; } mBluetoothGatt.close(); mBluetoothGatt = null; } /** * Request a read on a given {@code BluetoothGattCharacteristic}. The read result is reported * asynchronously through the {@code BluetoothGattCallback#onCharacteristicRead(android.bluetooth.BluetoothGatt, android.bluetooth.BluetoothGattCharacteristic, int)} * callback. * * @param characteristic The characteristic to read from. */ public void readCharacteristic(BluetoothGattCharacteristic characteristic) { if (mBluetoothAdapter == null || mBluetoothGatt == null) { Log.w(TAG, "BluetoothAdapter not initialized"); return; } mBluetoothGatt.readCharacteristic(characteristic); } /** * Enables or disables notification on a give characteristic. * * @param characteristic Characteristic to act on. * @param enabled If true, enable notification. False otherwise. */ public void setCharacteristicNotification(BluetoothGattCharacteristic characteristic,//便於更新數據 boolean enabled) { if (mBluetoothAdapter == null || mBluetoothGatt == null) { Log.w(TAG, "BluetoothAdapter not initialized"); return; } mBluetoothGatt.setCharacteristicNotification(characteristic, enabled); } public void writeCharacteristic(BluetoothGattCharacteristic characteristic){ mBluetoothGatt.writeCharacteristic(characteristic); } /** * Retrieves a list of supported GATT services on the connected device. This should be * invoked only after {@code BluetoothGatt#discoverServices()} completes successfully. * * @return A {@code List} of supported services. */ public BluetoothGattService getSupportedGattService() {//根據service的UUID來獲取service if (mBluetoothGatt == null) return null; return mBluetoothGatt.getService(UUID.fromString("0000fff0-0000-1000-8000-00805f9b34fb")); } }
public class Utils { private static HashMap<Integer, String> serviceTypes = new HashMap(); static { // Sample Services. serviceTypes.put(BluetoothGattService.SERVICE_TYPE_PRIMARY, "PRIMARY"); serviceTypes.put(BluetoothGattService.SERVICE_TYPE_SECONDARY, "SECONDARY"); } public static String getServiceType(int type){ return serviceTypes.get(type); } //------------------------------------------- private static HashMap<Integer, String> charPermissions = new HashMap(); static { charPermissions.put(0, "UNKNOW"); charPermissions.put(BluetoothGattCharacteristic.PERMISSION_READ, "READ"); charPermissions.put(BluetoothGattCharacteristic.PERMISSION_READ_ENCRYPTED, "READ_ENCRYPTED"); charPermissions.put(BluetoothGattCharacteristic.PERMISSION_READ_ENCRYPTED_MITM, "READ_ENCRYPTED_MITM"); charPermissions.put(BluetoothGattCharacteristic.PERMISSION_WRITE, "WRITE"); charPermissions.put(BluetoothGattCharacteristic.PERMISSION_WRITE_ENCRYPTED, "WRITE_ENCRYPTED"); charPermissions.put(BluetoothGattCharacteristic.PERMISSION_WRITE_ENCRYPTED_MITM, "WRITE_ENCRYPTED_MITM"); charPermissions.put(BluetoothGattCharacteristic.PERMISSION_WRITE_SIGNED, "WRITE_SIGNED"); charPermissions.put(BluetoothGattCharacteristic.PERMISSION_WRITE_SIGNED_MITM, "WRITE_SIGNED_MITM"); } public static String getCharPermission(int permission){ return getHashMapValue(charPermissions,permission); } //------------------------------------------- private static HashMap<Integer, String> charProperties = new HashMap(); static { charProperties.put(BluetoothGattCharacteristic.PROPERTY_BROADCAST, "BROADCAST"); charProperties.put(BluetoothGattCharacteristic.PROPERTY_EXTENDED_PROPS, "EXTENDED_PROPS"); charProperties.put(BluetoothGattCharacteristic.PROPERTY_INDICATE, "INDICATE"); charProperties.put(BluetoothGattCharacteristic.PROPERTY_NOTIFY, "NOTIFY"); charProperties.put(BluetoothGattCharacteristic.PROPERTY_READ, "READ"); charProperties.put(BluetoothGattCharacteristic.PROPERTY_SIGNED_WRITE, "SIGNED_WRITE"); charProperties.put(BluetoothGattCharacteristic.PROPERTY_WRITE, "WRITE"); charProperties.put(BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE, "WRITE_NO_RESPONSE"); } public static String getCharPropertie(int property){ return getHashMapValue(charProperties,property); } //-------------------------------------------------------------------------- private static HashMap<Integer, String> descPermissions = new HashMap(); static { descPermissions.put(0, "UNKNOW"); descPermissions.put(BluetoothGattDescriptor.PERMISSION_READ, "READ"); descPermissions.put(BluetoothGattDescriptor.PERMISSION_READ_ENCRYPTED, "READ_ENCRYPTED"); descPermissions.put(BluetoothGattDescriptor.PERMISSION_READ_ENCRYPTED_MITM, "READ_ENCRYPTED_MITM"); descPermissions.put(BluetoothGattDescriptor.PERMISSION_WRITE, "WRITE"); descPermissions.put(BluetoothGattDescriptor.PERMISSION_WRITE_ENCRYPTED, "WRITE_ENCRYPTED"); descPermissions.put(BluetoothGattDescriptor.PERMISSION_WRITE_ENCRYPTED_MITM, "WRITE_ENCRYPTED_MITM"); descPermissions.put(BluetoothGattDescriptor.PERMISSION_WRITE_SIGNED, "WRITE_SIGNED"); descPermissions.put(BluetoothGattDescriptor.PERMISSION_WRITE_SIGNED_MITM, "WRITE_SIGNED_MITM"); } public static String getDescPermission(int property){ return getHashMapValue(descPermissions,property); } //這段代碼沒看明白,歡迎大神指教 private static String getHashMapValue(HashMap<Integer, String> hashMap,int number){ String result =hashMap.get(number); if(TextUtils.isEmpty(result)){ List<Integer> numbers = getElement(number); result=""; for(int i=0;i<numbers.size();i++){ result+=hashMap.get(numbers.get(i))+"|"; } } return result; } /** * 位運算結果的反推函數10 -> 2 | 8; */ static private List<Integer> getElement(int number){ List<Integer> result = new ArrayList<Integer>(); for (int i = 0; i < 32; i++){ int b = 1 << i; if ((number & b) > 0) result.add(b); } return result; } public static String bytesToHexString(byte[] src){ StringBuilder stringBuilder = new StringBuilder(""); if (src == null || src.length <= 0) { return null; } for (int i = 0; i < src.length; i++) { int v = src[i] & 0xFF; String hv = Integer.toHexString(v); if (hv.length() < 2) { stringBuilder.append(0); } stringBuilder.append(hv); } return stringBuilder.toString(); } }