以编程方式与Android 4.4+上的BLE设备配对

有没有人有一个完整的工作示例,说明如何以编程方式与在Android 4.4或更高版本上使用密码输入(即6位数PIN)或数字比较的BLE( 蓝牙经典)设备配对? 通过’以编程方式’我的意思是我告诉Android PIN – 用户不会被提示。

在SO上有很多类似的问题,但它们要么a)关于Bluetooth Classic,b)old(在setPin()createBond()是公开的),或者c)没有答案。

我的理解如下。

  1. 您连接到设备并发现其服务。
  2. 您尝试阅读“受保护”特征。
  3. 设备返回身份validation错误。
  4. Android以某种方式启动配对,你告诉它PIN。
  5. 您现在可以阅读该特征。

我已经使用在nRF51-DK上运行的mBed创建了一个设备,并赋予它一个特性。

我设置了安全参数,如下所示:

 ble.securityManager().init( true, // Enable bonding (though I don't really need this) true, // Require MitM protection. I assume you don't get a PIN prompt without this, though I'm not 100% sure. SecurityManager::IO_CAPS_DISPLAY_ONLY, // This makes it us the Passkey Entry (PIN) pairing method. "123456"); // Static PIN 

然后在我使用的特征

 requireSecurity(SecurityManager::SECURITY_MODE_ENCRYPTION_WITH_MITM); 

现在,当我尝试使用Nordic Master控制面板阅读时,我收到如下配对请求通知:

配对请求

密码输入

我可以把这个PIN放入,然后MCP说我已经粘合,并且可以读取该特性。

但是,在我的应用程序中,我想避免让用户输入PIN,因为我已经知道了。 有没有人有一个完整的最近如何做到这一点的例子?

编辑 :顺便说一下, 这是我在SO上find的最相关的问题,但那里的答案似乎不起作用。

Solutions Collecting From Web of "以编程方式与Android 4.4+上的BLE设备配对"

几乎让它工作了。 它以编程方式配对,但我无法摆脱“配对请求”通知。 这个问题的一些答案声称能够在使用隐藏方法cancelPairingUserInput()显示它之后隐藏它,但这似乎对我不起作用。

编辑:成功!

我最终使用了读取BluetoothPairingRequest的源代码和发送配对请求广播的代码,并意识到我应该拦截ACTION_PAIRING_REQUEST 。 幸运的是,这是一个有序的意图广播,所以你可以在系统之前拦截它。

这是程序。

  1. 注册接收BluetoothDevice.ACTION_PAIRING_REQUEST更改广播意图。 使用高优先级!
  2. 连接到设备。
  3. 发现服务。
  4. 如果您现在已经断开连接,可能是因为绑定信息不正确(例如外围设备已清除它)。 在这种情况下,使用隐藏方法(严重Google)删除绑定信息,然后重新连接。
  5. 尝试阅读需要加密MitM保护的特性。
  6. ACTION_PAIRING_REQUEST广播接收器中,检查配对types是否为BluetoothDevice.PAIRING_VARIANT_PIN ,如果是,则调用setPin()abortBroadcast() 。 否则你可以让系统处理它,或者显示错误或其他什么。

这是代码。

 /* This implements the BLE connection logic. Things to watch out for: 1. If the bond information is wrong (eg it has been deleted on the peripheral) then discoverServices() will cause a disconnect. You need to delete the bonding information and reconnect. 2. If the user ignores the PIN request, you get the undocumented GATT_AUTH_FAILED code. */ public class ConnectActivityLogic extends Fragment { // The connection to the device, if we are connected. private BluetoothGatt mGatt; // This is used to allow GUI fragments to subscribe to state change notifications. public static class StateObservable extends Observable { private void notifyChanged() { setChanged(); notifyObservers(); } }; // When the logic state changes, State.notifyObservers(this) is called. public final StateObservable State = new StateObservable(); public ConnectActivityLogic() { } @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // Tell the framework to try to keep this fragment around // during a configuration change. setRetainInstance(true); // Actually set it in response to ACTION_PAIRING_REQUEST. final IntentFilter pairingRequestFilter = new IntentFilter(BluetoothDevice.ACTION_PAIRING_REQUEST); pairingRequestFilter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY - 1); getActivity().getApplicationContext().registerReceiver(mPairingRequestRecevier, pairingRequestFilter); // Update the UI. State.notifyChanged(); // Note that we don't actually need to request permission - all apps get BLUETOOTH and BLUETOOTH_ADMIN permissions. // LOCATION_COARSE is only used for scanning which I don't need (MAC is hard-coded). // Connect to the device. connectGatt(); } @Override public void onDestroy() { super.onDestroy(); // Disconnect from the device if we're still connected. disconnectGatt(); // Unregister the broadcast receiver. getActivity().getApplicationContext().unregisterReceiver(mPairingRequestRecevier); } // The state used by the UI to show connection progress. public ConnectionState getConnectionState() { return mState; } // Internal state machine. public enum ConnectionState { IDLE, CONNECT_GATT, DISCOVER_SERVICES, READ_CHARACTERISTIC, FAILED, SUCCEEDED, } private ConnectionState mState = ConnectionState.IDLE; // When this fragment is created it is given the MAC address and PIN to connect to. public byte[] macAddress() { return getArguments().getByteArray("mac"); } public int pinCode() { return getArguments().getInt("pin", -1); } // Start the connection process. private void connectGatt() { // Disconnect if we are already connected. disconnectGatt(); // Update state. mState = ConnectionState.CONNECT_GATT; State.notifyChanged(); BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(macAddress()); // Connect! mGatt = device.connectGatt(getActivity(), false, mBleCallback); } private void disconnectGatt() { if (mGatt != null) { mGatt.disconnect(); mGatt.close(); mGatt = null; } } // See https://android.googlesource.com/platform/external/bluetooth/bluedroid/+/master/stack/include/gatt_api.h private static final int GATT_ERROR = 0x85; private static final int GATT_AUTH_FAIL = 0x89; private android.bluetooth.BluetoothGattCallback mBleCallback = new BluetoothGattCallback() { @Override public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) { super.onConnectionStateChange(gatt, status, newState); switch (newState) { case BluetoothProfile.STATE_CONNECTED: // Connected to the device. Try to discover services. if (gatt.discoverServices()) { // Update state. mState = ConnectionState.DISCOVER_SERVICES; State.notifyChanged(); } else { // Couldn't discover services for some reason. Fail. disconnectGatt(); mState = ConnectionState.FAILED; State.notifyChanged(); } break; case BluetoothProfile.STATE_DISCONNECTED: // If we try to discover services while bonded it seems to disconnect. // We need to debond and rebond... switch (mState) { case IDLE: // Do nothing in this case. break; case CONNECT_GATT: // This can happen if the bond information is incorrect. Delete it and reconnect. deleteBondInformation(gatt.getDevice()); connectGatt(); break; case DISCOVER_SERVICES: // This can also happen if the bond information is incorrect. Delete it and reconnect. deleteBondInformation(gatt.getDevice()); connectGatt(); break; case READ_CHARACTERISTIC: // Disconnected while reading the characteristic. Probably just a link failure. gatt.close(); mState = ConnectionState.FAILED; State.notifyChanged(); break; case FAILED: case SUCCEEDED: // Normal disconnection. break; } break; } } @Override public void onServicesDiscovered(BluetoothGatt gatt, int status) { super.onServicesDiscovered(gatt, status); // Services have been discovered. Now I try to read a characteristic that requires MitM protection. // This triggers pairing and bonding. BluetoothGattService nameService = gatt.getService(UUIDs.NAME_SERVICE); if (nameService == null) { // Service not found. disconnectGatt(); mState = ConnectionState.FAILED; State.notifyChanged(); return; } BluetoothGattCharacteristic characteristic = nameService.getCharacteristic(UUIDs.NAME_CHARACTERISTIC); if (characteristic == null) { // Characteristic not found. disconnectGatt(); mState = ConnectionState.FAILED; State.notifyChanged(); return; } // Read the characteristic. gatt.readCharacteristic(characteristic); mState = ConnectionState.READ_CHARACTERISTIC; State.notifyChanged(); } @Override public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) { super.onCharacteristicRead(gatt, characteristic, status); if (status == BluetoothGatt.GATT_SUCCESS) { // Characteristic read. Check it is the right one. if (!UUIDs.NAME_CHARACTERISTIC.equals(characteristic.getUuid())) { // Read the wrong characteristic. This shouldn't happen. disconnectGatt(); mState = ConnectionState.FAILED; State.notifyChanged(); return; } // Get the name (the characteristic I am reading just contains the device name). byte[] value = characteristic.getValue(); if (value == null) { // Hmm... } disconnectGatt(); mState = ConnectionState.SUCCEEDED; State.notifyChanged(); // Success! Save it to the database or whatever... } else if (status == BluetoothGatt.GATT_INSUFFICIENT_AUTHENTICATION) { // This is where the tricky part comes if (gatt.getDevice().getBondState() == BluetoothDevice.BOND_NONE) { // Bonding required. // The broadcast receiver should be called. } else { // ? } } else if (status == GATT_AUTH_FAIL) { // This can happen because the user ignored the pairing request notification for too long. // Or presumably if they put the wrong PIN in. disconnectGatt(); mState = ConnectionState.FAILED; State.notifyChanged(); } else if (status == GATT_ERROR) { // I thought this happened if the bond information was wrong, but now I'm not sure. disconnectGatt(); mState = ConnectionState.FAILED; State.notifyChanged(); } else { // That's weird. disconnectGatt(); mState = ConnectionState.FAILED; State.notifyChanged(); } } }; private final BroadcastReceiver mPairingRequestRecevier = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { if (BluetoothDevice.ACTION_PAIRING_REQUEST.equals(intent.getAction())) { final BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); int type = intent.getIntExtra(BluetoothDevice.EXTRA_PAIRING_VARIANT, BluetoothDevice.ERROR); if (type == BluetoothDevice.PAIRING_VARIANT_PIN) { device.setPin(Util.IntToPasskey(pinCode())); abortBroadcast(); } else { Lw("Unexpected pairing type: " + type); } } } }; public static void deleteBondInformation(BluetoothDevice device) { try { // FFS Google, just unhide the method. Method m = device.getClass().getMethod("removeBond", (Class[]) null); m.invoke(device, (Object[]) null); } catch (Exception e) { Le(e.getMessage()); } } } 

我也遇到了同样的问题,经过所有的研究,我发现了下面的解决方案,在没有任何人工干预的情况下与BLE配对。

(经过测试和工作!!!)

我基本上是在寻找一个特定的蓝牙设备(我知道MAC地址),并在find后与它配对。 首先要做的是使用广播接收器创建配对请求,并按如下方式处理请求。

 IntentFilter intentFilter = new IntentFilter(BluetoothDevice.ACTION_PAIRING_REQUEST); intentFilter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY); registerReceiver(broadCastReceiver,intentFilter); 

您需要编写broadcastReceiver并按如下方式处理它。

 String BLE_PIN = "1234" private BroadcastReceiver broadCastReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); if(BluetoothDevice.ACTION_PAIRING_REQUEST.equals(action)) { BluetoothDevice bluetoothDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); bluetoothDevice.setPin(BLE_PIN.getBytes()); Log.e(TAG,"Auto-entering pin: " + BLE_PIN); bluetoothDevice.createBond(); Log.e(TAG,"pin entered and request sent..."); } } }; 

瞧! 您应该能够在没有任何手动干预的情况下与蓝牙设备配对。

希望这会有所帮助:-)如果适合您,请正确回答。