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

时间:2016-06-27 13:48:58

标签: android bluetooth bluetooth-lowenergy pairing

有没有人有一个完整的工作示例,说明如何以编程方式配对使用密钥输入(即6位数PIN)或Android 4.4上的数字比较的BLE( Bluetooth Classic)设备或后来?通过'以编程方式'我的意思是我告诉Android PIN - 用户不会被提示。

在SO上有很多类似的问题,但它们要么a)关于蓝牙经典,b)旧(setPin()createBond()公开之前),或c)未答复。

我的理解如下。

  1. 您连接到设备并发现其服务。
  2. 您尝试阅读'受保护'特征。
  3. 设备返回验证错误。
  4. Android以某种方式启动配对并告诉它PIN。
  5. 您现在可以阅读特征。
  6. 我使用mBed上运行的nRF51-DK创建了一个设备,并赋予它一个特征。

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

    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 Control Panel阅读时,我会收到如下配对请求通知:

    pairing request

    passkey entry

    我可以把这个PIN放入,然后MCP说我已经绑定了,可以读取这个特性。

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

    编辑:顺便提一句this是我在SO上找到的最相关的问题,但答案似乎不起作用。

2 个答案:

答案 0 :(得分:31)

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

编辑:成功!

我最终使用了BluetoothPairingRequestthe code that sends the pairing request broadcast的源代码,并意识到我应该拦截ACTION_PAIRING_REQUEST。幸运的是,它是一个有序的意图广播,所以你可以在系统之前拦截它。

这是程序。

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

    /* This implements the BLE connection logic. Things to watch out for:
    
    1. If the bond information is wrong (e.g. 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
                    {
                        L.w("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)
            {
                L.e(e.getMessage());
            }
        }
    }
    

答案 1 :(得分:3)

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

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

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

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...");
        }
    }
};

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

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