Android从4.3(Api level 18)开始支持BLE的开发,本文记录了Android 4.4.2设备与BLE设备通讯的流程。
权限需求:
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
步骤:
1.获取蓝牙服务
BluetoothManager btManager= (BluetoothManager) this.getSystemService(Context.BLUETOOTH_SERVICE);
2.获取蓝牙设备管理器(适配器)
BluetoothAdapter mBluetoothAdapter = btManager.getAdapter();
3.判断设备是否支持BLE蓝牙通讯,支持则判断蓝牙是否已开启,未开启蓝牙的设置开启。
if (mBluetoothAdapter != null && (!mBluetoothAdapter.isEnabled())){
mBluetoothAdapter.enable();//打开蓝牙
}
4.扫描当前附近的蓝牙BLE设备(这个操作比较耗电,要适时地停止)
5.停止设备扫描
6.与指定BLE设备建立Gatt连接
7.在连接成功后,设置扫描BLE设备支持的service和characteristic,并设置。这里需要注意,需要开启通知的,需要设置相应描述符。
8.读写操作
9.断开Gatt连接
10.关闭Gatt连接
在开发过程中,遇到的主要问题是onCharacteristicWrite失败回调,这个可能跟低功耗蓝牙外设有关,需要断开并关闭Gatt连接重新建立
下面是主要参考代码
package com.rollup.bluetoothlock; import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.List; import java.util.Timer; import java.util.TimerTask; import java.util.UUID; import com.android.peephole.MyApplication; import com.rollup.bluetoothlock.MyLog; import com.rollup.bluetoothlock.Utils; import com.rollup.bluetoothlock.Value; import android.R.integer; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothAdapter.LeScanCallback; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothGatt; import android.bluetooth.BluetoothGattCallback; import android.bluetooth.BluetoothGattCharacteristic; import android.bluetooth.BluetoothGattDescriptor; import android.bluetooth.BluetoothGattService; import android.bluetooth.BluetoothManager; import android.bluetooth.BluetoothProfile; import android.content.Context; import android.content.SharedPreferences; import android.content.SharedPreferences.Editor; import android.os.Handler; import android.os.Message; import android.widget.EditText; import android.widget.Toast; public class BleManager { private static final String TAG = "BleManager"; public static final int ERROR_NO_ERROR = 0;//成功 public static final int ERROR_BTDEVICE_EXC_FAIL = 1;// 蓝牙锁端执行错误 public static final int ERROR_PSD_ERROR = 2; // 钥匙错误 public static final int ERROR_CONNECT_FAIL = 3; public static final int ERROR_DEFAULT = 4; public static final int ERROR_NOT_FOUND_DEVICE = 5;// 未扫描到指定编码的锁 public static final int ERROR_HAVE_CONNECTED = 6;//上次连接未执行完毕 public static final int ERROR_NO_DEVICE = 7;//设备没有初始化 public static final int ERROR_CREATE_FAIL = 8; private static final int STOP_LESCAN = 1; private final int OVER_TIMER = 60 * 1000;// 扫描超时时间 private Context mContext = null; private BluetoothAdapter mBluetoothAdapter = null; private BluetoothManager mBluetoothManager = null; public BluetoothDevice mBluetoothDevice = null; private BluetoothGatt mBluetoothGatt; private static boolean isScanning = false; private BluetoothGattCharacteristic writeCharacteristic = null; private static boolean isConnectToDevice = false; private String btName = null; private String psd = null; private boolean hasExcuteOpenDoor = false;// 开门命令已执行 private byte[] devceKey; private static boolean haveRetry = false; private static BleManager instance = null; private MyHandler mHandler = null; public static long lastOpendoorTime = 0; private OnBleScanOpenDoorCallback openDoorCallback = null; public interface OnBleScanOpenDoorCallback { public void openDoorSuc(); public void openDoorFail(int error); } private BleManager(Context context, String btName, String psd) { this.mContext = context; initBluetooth(); if (mBluetoothManager == null) { MyLog.e(TAG, "Unable to initialize BluetoothManager."); return; } this.btName = btName; this.psd = psd; devceKey = new byte[16]; } public static BleManager getInstance(String btName, String psd) { if (instance == null) { instance = new BleManager(MyApplication.getMyApplication(), btName, psd); } else { instance.setBTinfo(btName, psd); } return instance; } private void setBTinfo(String name, String psd) { this.btName = name; this.psd = psd; } private void initBluetooth() { mHandler = new MyHandler(this); mBluetoothManager = (BluetoothManager) mContext .getSystemService(Context.BLUETOOTH_SERVICE); if (mBluetoothManager != null) { mBluetoothAdapter = mBluetoothManager.getAdapter(); if (mBluetoothAdapter != null) { if (!mBluetoothAdapter.isEnabled()) { mBluetoothAdapter.enable(); // 打开蓝牙 } } } } /** * 是否空闲可用 * @return */ public static boolean isFree(){ return (!isConnectToDevice)&&(!isScanning); } public void startLeScan(boolean autoConnect) { if (mBluetoothAdapter == null) { return; } if (btName == null || btName.length() == 0) { return; } if (isScanning) { return; } isScanning = true; if (autoConnect) { mBluetoothAdapter.startLeScan(mLeScanCallback2); } else { mBluetoothAdapter.startLeScan(mLeScanCallback); // 此mLeScanCallback为回调函数 } mHandler.sendEmptyMessageDelayed(STOP_LESCAN, OVER_TIMER); // 这个搜索30秒,如果搜索不到则停止搜索 } private void stopLeScan() { if (mHandler != null && mHandler.hasMessages(STOP_LESCAN)) { mHandler.removeMessages(STOP_LESCAN); } if (mBluetoothAdapter == null) { return; } if (!isScanning) { return; } mBluetoothAdapter.stopLeScan(mLeScanCallback); mBluetoothAdapter.stopLeScan(mLeScanCallback2); isScanning = false; } private LeScanCallback mLeScanCallback = new LeScanCallback() { @Override public void onLeScan(BluetoothDevice device, int arg1, byte[] arg2) { MyLog.i(TAG, "onLeScan() DeviceName------>" + device.getName()); // 在这里可通过device这个对象来获取到搜索到的ble设备名称和一些相关信息 if (device.getAddress() != null) { if (device.getName().toLowerCase().replace("_", "") .contentEquals(btName.toLowerCase().replace(" ", ""))) { mBluetoothDevice = device; stopLeScan(); } } } }; private LeScanCallback mLeScanCallback2 = new LeScanCallback() { @Override public void onLeScan(BluetoothDevice device, int arg1, byte[] arg2) { MyLog.i(TAG, "onLeScan() DeviceName------>" + device.getName()); // 在这里可通过device这个对象来获取到搜索到的ble设备名称和一些相关信息 if (device.getAddress() != null) { if (device.getName().toLowerCase().replace("_", "") .contentEquals(btName.toLowerCase().replace(" ", ""))) { mBluetoothDevice = device; stopLeScan(); connect(); } } } }; private static class MyHandler extends Handler { WeakReference<BleManager> wf = null; public MyHandler(BleManager manager) { // TODO Auto-generated constructor stub wf = new WeakReference<BleManager>(manager); } @Override public void handleMessage(Message msg) { // TODO Auto-generated method stub switch (msg.what) { case STOP_LESCAN: MyLog.i(TAG, "MyHandler handleMessage STOP_LESCAN"); if (wf.get()!=null){ wf.get().stopLeScan(); } MyLog.i(TAG, "release(), Scan time is up"); break; } } } public int connect() { if (mBluetoothDevice == null) { MyLog.i(TAG, "BluetoothDevice is null."); return ERROR_NO_DEVICE; } if(isConnectToDevice){ MyLog.i(TAG, "Have connected to device"); return ERROR_HAVE_CONNECTED; } isConnectToDevice = true; hasExcuteOpenDoor = false; // 两个设备通过BLE通信,首先需要建立GATT连接。这里我们讲的是Android设备作为client端,连接GATT Server mBluetoothGatt = mBluetoothDevice.connectGatt(mContext, false, mGattCallback); // mGattCallback为回调接口 if (mBluetoothGatt != null) { if (mBluetoothGatt.connect()) { MyLog.d(TAG, "Connect succeed."); return ERROR_NO_ERROR; } else { MyLog.d(TAG, "Connect fail."); return ERROR_CONNECT_FAIL; } } else { MyLog.d(TAG, "BluetoothGatt null."); return ERROR_CREATE_FAIL; } } private final BluetoothGattCallback mGattCallback = new BluetoothGattCallback() { @Override public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) { if (newState == BluetoothProfile.STATE_CONNECTED) { isConnectToDevice = true; gatt.discoverServices(); // 执行到这里其实蓝牙已经连接成功了 MyLog.i(TAG, "Connected to GATT server."); } else if (newState == BluetoothProfile.STATE_DISCONNECTED) { if (!hasExcuteOpenDoor) { disconnect(); retryUnlock(); if (!haveRetry) { //已失败重试过 if (openDoorCallback != null) { openDoorCallback.openDoorFail(ERROR_CONNECT_FAIL); } } } else { MyLog.i(TAG, "BluetoothProfile.STATE_DISCONNECTED hasExcuteOpenDoor=TRUE"); haveRetry = false; disconnect(); } MyLog.i(TAG, "BluetoothProfile.STATE_DISCONNECTED --release()"); } } public void onServicesDiscovered(BluetoothGatt gatt, int status) { if (status == BluetoothGatt.GATT_SUCCESS) { MyLog.i(TAG, "onServicesDiscovered"); List<BluetoothGattService> services = gatt.getServices(); for (int i = 0; i < services.size(); i++) { MyLog.i(TAG, "[Service " + i + "] uuid:" + services.get(i).getUuid()); if (services.get(i).getUuid().equals(Value.uuid)) { List<BluetoothGattCharacteristic> characteristics = services .get(i).getCharacteristics(); for (int j = 0; j < characteristics.size(); j++) { MyLog.i(TAG, "[Characteristic]" + characteristics.get(j).getUuid()); if (characteristics.get(j).getUuid() .equals(Value.CHARACTERISTIC_READ)) { boolean res = mBluetoothGatt .setCharacteristicNotification( characteristics.get(j), true); // 打开通知描述 for (BluetoothGattDescriptor descriptor : characteristics .get(j).getDescriptors()) { descriptor .setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE); gatt.writeDescriptor(descriptor); MyLog.i(TAG, "Characteristic set notification is Success!"); } MyLog.i(TAG, "onServicesDiscovered setCharacteristicNotification result=" + res); break; } } characteristics = null; } } for (int i = 0; i < services.size(); i++) { MyLog.i(TAG, "[Service " + i + "] uuid:" + services.get(i).getUuid()); if (services.get(i).getUuid().equals(Value.uuid)) { List<BluetoothGattCharacteristic> characteristics = services .get(i).getCharacteristics(); for (int j = 0; j < characteristics.size(); j++) { MyLog.i(TAG, "[Characteristic]" + characteristics.get(j).getUuid()); if (characteristics.get(j).getUuid() .equals(Value.CHARACTERISTIC_WRITE)) { writeCharacteristic = characteristics.get(j); final int charaProp = characteristics.get(j) .getProperties(); // 如果该char可写 if ((charaProp | BluetoothGattCharacteristic.PROPERTY_READ) > 0) { byte[] value = new byte[] { 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; characteristics.get(j).setValue(value); writeCharacteristic .setWriteType(BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT); mBluetoothGatt .writeCharacteristic(characteristics .get(j)); value = null; } break; } } characteristics = null; } } services = null; } else { MyLog.i(TAG, "onServicesDiscovered status------>" + status); } } @Override public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) { MyLog.i(TAG, "onCharacteristicRead------>" + Utils.bytesToHexString(characteristic.getValue())); } @Override public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) { MyLog.i(TAG, "onCharacteristicChanged------>" + Utils.bytesToHexString(characteristic.getValue())); MyLog.i(TAG, "UUID------>" + characteristic.getUuid().toString()); if (Value.CHARACTERISTIC_READ.equals(characteristic.getUuid())) { byte[] value = characteristic.getValue(); if ((value[0] & 0xFF) == 0x81) { for (int i = 0; i < 16; i++) { devceKey[i] = value[i + 1]; } // MyLog.i(TAG, "加密秘钥为:"+Utils.bytesToHexString(devceKey)); if (writeCharacteristic != null) { byte[] cmd = new byte[] { 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; if (psd.length() == 8) { int j = 0; for (int i = 0; i < 4; i++) { cmd[5 + i] = (byte) (((((Integer.valueOf(psd .charAt(j)) - 48) << 4) & (0xF0)) + ((Integer .valueOf(psd.charAt(j + 1)) - 48) & 0x0F)) & 0xFF); MyLog.i(TAG, "cmd" + (9 + i) + "=" + cmd[9 + i]); j = j + 2; } } byte[] cmd_encrypt = Utils.enCode(devceKey, cmd); if (cmd_encrypt != null) { byte[] request = new byte[17]; request[0] = 0x03; for (int i = 1; i < (cmd_encrypt.length < request.length ? cmd_encrypt.length : request.length); i++) { request[i] = cmd_encrypt[i - 1]; } writeCharacteristic.setValue(request); writeCharacteristic .setWriteType(BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT); // MyLog.i(TAG, // "发送开锁命令:"+Utils.bytesToHexString(request)); mBluetoothGatt .writeCharacteristic(writeCharacteristic); request = null; } cmd_encrypt = null; cmd = null; } } else if ((value[0] & 0xFF) == 0x83) { byte[] res = new byte[16]; for (int i = 0; i < 16; i++) { res[i] = value[i + 1]; } // MyLog.i(TAG, "解密秘钥为:"+Utils.bytesToHexString(devceKey)); // MyLog.i(TAG, "待解密数据为:"+Utils.bytesToHexString(res)); byte[] decode_res = Utils.deCode(devceKey, res); hasExcuteOpenDoor = true; if (decode_res != null) { MyLog.i(TAG, "获取到蓝牙开锁返回数据:" + Utils.bytesToHexString(decode_res)); if ((decode_res[0] & 0xFF) == 0x01) { if ((decode_res[1] & 0xFF) == 0x00) { MyLog.i(TAG, "开锁成功"); if (openDoorCallback != null) { openDoorCallback.openDoorSuc(); } lastOpendoorTime = System.currentTimeMillis(); } else if ((decode_res[1] & 0xFF) == 0x01) { MyLog.i(TAG, "开锁失败:命令执行失败"); if (openDoorCallback != null) { openDoorCallback .openDoorFail(ERROR_BTDEVICE_EXC_FAIL); } } else if ((decode_res[1] & 0xFF) == 0x02) { MyLog.i(TAG, "开锁失败:密钥解密失败,无效用户密钥"); if (openDoorCallback != null) { openDoorCallback .openDoorFail(ERROR_PSD_ERROR); } } else { MyLog.e(TAG, "开锁返回异常"); if (openDoorCallback != null) { openDoorCallback .openDoorFail(ERROR_DEFAULT); } } new Timer().schedule(new TimerTask() { @Override public void run() { // TODO Auto-generated method stub sendEndCmd(); } }, 500); } else { MyLog.e(TAG, "开锁返回动作标志位异常,此时锁还会再次返回其他数据"); if (openDoorCallback != null) { openDoorCallback.openDoorFail(ERROR_DEFAULT); } } } else { MyLog.e(TAG, "蓝牙开锁数据获取失败"); if (openDoorCallback != null) { openDoorCallback.openDoorFail(ERROR_DEFAULT); } // disconnect(true); } decode_res = null; res = null; } else { MyLog.e(TAG, " value[0] = " + value[0] + " ===release() 11111111"); // disconnect(true); } value = null; } } // 接受Characteristic被写的通知,收到蓝牙模块的数据后会触发onCharacteristicWrite @Override public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) { MyLog.i(TAG, "onCharacteristicWrite status = " + status + ",onCharacteristicWrite------>" + Utils.bytesToHexString(characteristic.getValue())); if (status != 0) { // disconnect(true); } } }; private void sendEndCmd() { if (writeCharacteristic != null) { byte[] cmd = new byte[] { 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; byte[] cmd_encrypt = Utils.enCode(devceKey, cmd); if (cmd_encrypt != null) { byte[] request = new byte[17]; request[0] = 0x08; for (int i = 1; i < (cmd_encrypt.length < request.length ? cmd_encrypt.length : request.length); i++) { request[i] = cmd_encrypt[i - 1]; } writeCharacteristic.setValue(request); writeCharacteristic .setWriteType(BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE); mBluetoothGatt.writeCharacteristic(writeCharacteristic); request = null; } cmd = null; cmd_encrypt = null; } // release(); } private void retryUnlock() { if (!haveRetry) { haveRetry = true; connect(); } else { haveRetry = false; MyLog.i(TAG, "retryUnlock haveRetry"); } } public void setOpenDoorCallback(OnBleScanOpenDoorCallback callback) { openDoorCallback = callback; } public synchronized void disconnect() { MyLog.i(TAG, "disconnect"); if (mBluetoothGatt != null) { mBluetoothGatt.disconnect(); mBluetoothGatt.close(); mBluetoothGatt = null; } isConnectToDevice = false; } }