在Android上使用一个BLE装置配对编程4.4或更高 [英] Programmatically pairing with a BLE device on Android 4.4+

查看:1507
本文介绍了在Android上使用一个BLE装置配对编程4.4或更高的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

有没有人对如何编程有BLE对(的的蓝牙经典)设备使用密钥输入(即6位数的PIN),或在Android 4.4数值比较或一个完整的工作示例后来?通过编程我的意思是,我告诉Android的PIN码 - 用户不会提示

Does anyone have a complete working example of how to programmatically pair with a BLE (not Bluetooth Classic) device that uses passkey entry (i.e. a 6-digit PIN) or Numeric Comparison on Android 4.4 or later? By 'programmatically' I mean I tell Android the PIN - the user isn't prompted.

有对SO这个很多类似的问题,但他们无论是)关于蓝牙经典,B)老(前<一href=\"https://developer.android.com/reference/android/bluetooth/BluetoothDevice.html#setPin(byte[])\"><$c$c>setPin()和<一个href=\"https://developer.android.com/reference/android/bluetooth/BluetoothDevice.html#createBond()\"><$c$c>createBond()是公开的),或c)无人接听。

There are many similar questions about this on SO but they are either a) about Bluetooth Classic, b) old (before setPin() and createBond() were public), or c) unanswered.

我的理解如下:


  1. 您连接到该设备,并发现它的服务。

  2. 您尝试读取一个受保护的特征。

  3. 设备返回一个验证错误。

  4. 的Andr​​oid不知何故开始配对,你告诉它的PIN码。

  5. 您现在可以阅读的特点。

我已经用 mBed 的上的 nRF51-DK ,并给它一个特点。

I have created a device using mBed running on the nRF51-DK and given it a single characteristic.

我设置的安全性参数,像这样:

I set up the security parameters like so:

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

然后在我使用的特征

And then in the characteristic I used

requireSecurity(SecurityManager::SECURITY_MODE_ENCRYPTION_WITH_MITM);

现在,当我尝试用北欧主控制面板,我得到这样的配对请求通知:

Now when I try to read it with the Nordic Master Control Panel, I get a pairing request notification like this:

和我可以把这个PIN码,然后MCP说我粘合,并且可以读取性能。

And I can put this PIN in, and then MCP says I'm bonded, and can read the characteristic.

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

However, in my app I would like to avoid having the user enter the PIN, since I know it already. Does anyone have a complete recent example of how to do this?

修改的:顺便问一下<一个href=\"http://stackoverflow.com/questions/31858774/programmatically-bonding-to-ble-device-on-android\">this是最相关的问题,我就发现SO,但答案似乎有没有工作。

Edit: By the way this is the most relevant question I found on SO, but the answer there doesn't seem to work.

推荐答案

<击>我的几乎的有工作。这对编程,但我无法摆脱的配对请求通知。一些这个问题的答案声称能够将其隐藏使用隐藏方法 cancelPairingUserInput()显示刚过,但似乎并没有为我工作。

I almost have it working. It pairs programmatically but I can't get rid of the "Pairing request" notification. Some answers to this question claim to be able to hide it just after it is shown using the hidden method cancelPairingUserInput() but that doesn't seem to work for me.

我最终使出阅读的<一个源$ C ​​$ C href=\"http://androidxref.com/6.0.1_r10/xref/packages/apps/Settings/src/com/android/settings/bluetooth/BluetoothPairingRequest.java\"相对=nofollow> BluetoothPairingRequest 和<一个href=\"http://androidxref.com/6.0.1_r10/xref/packages/apps/Bluetooth/src/com/android/bluetooth/btservice/BondStateMachine.java#307\"相对=nofollow>在code发送的配对请求广播并意识到我应该被拦截 ACTION_PAIRING_REQUEST 。幸运的是一个有序的意图广播所以系统做之前,你可以拦截它。

I eventually resorted to reading the source code of BluetoothPairingRequest and the code that sends the pairing request broadcast and realised I should be intercepting the ACTION_PAIRING_REQUEST. Fortunately it is an ordered intent broadcast so you can intercept it before the system does.

下面的步骤。


  1. 注册接收 BluetoothDevice.ACTION_PAIRING_REQUEST 改变广播意图。 使用高优先级的!

  2. 连接到设备上。

  3. 发现服务。

  4. 如果你现在断开,可能是因为债券信息不正确(如外围清除它)。在这种情况下,删除使用隐藏的方法(严重谷歌)债券的信息,并重新连接。

  5. 尝试读取需要加密保护的MITM一个特点。

  6. ACTION_PAIRING_REQUEST 广播接收器,检查配对形式是 BluetoothDevice.PAIRING_VARIANT_PIN ,如果是这样,请拨打 setPin() abortBroadcast()。否则,你可以让系统处理它,或显示错误或什么的。

  1. Register to receive BluetoothDevice.ACTION_PAIRING_REQUEST changed broadcast intents. Use a high priority!
  2. Connect to the device.
  3. Discover services.
  4. If you have disconnected by now, it's probably because the bond information is incorrect (e.g. the peripheral purged it). In that case, delete the bond information using a hidden method (seriously Google), and reconnect.
  5. Try to read a characteristic that requires encryption MitM protection.
  6. In the ACTION_PAIRING_REQUEST broadcast receiver, check that the pairing type is BluetoothDevice.PAIRING_VARIANT_PIN and if so, call setPin() and abortBroadcast(). Otherwise you can just let the system handle it, or show an error or whatever.

下面是code。

/* 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());
        }
    }
}

这篇关于在Android上使用一个BLE装置配对编程4.4或更高的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆