以编程方式与 Android 4.4+ 上的 BLE 设备配对 [英] Programmatically pairing with a BLE device on Android 4.4+

查看:14
本文介绍了以编程方式与 Android 4.4+ 上的 BLE 设备配对的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

有没有人有一个完整的工作示例,说明如何在 Android 4.4 或 Android 4.4 上以编程方式与使用密钥输入(即 6 位 PIN)或数字比较的 BLE(蓝牙经典)设备配对?之后?以编程方式"是指我将 PIN 告诉 Android - 不会提示用户.

SO 上有很多类似的问题,但它们要么是 a) 关于蓝牙经典,b) 旧的(在

然后我可以输入这个 PIN,然后 MCP 说我绑定了,并且可以读取特征.

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

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

解决方案

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

成功!

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

这是程序.

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

这是代码.

/* 这实现了 BLE 连接逻辑.需要注意的事项:1. 如果绑定信息有误(例如在外设上被删除了)则discoverServices() 将导致断开连接.您需要删除绑定信息并重新连接.2. 如果用户忽略 PIN 请求,您将获得未记录的 GATT_AUTH_FAILED 代码.*/公共类 ConnectActivityLogic 扩展了 Fragment{//与设备的连接,如果我们已连接.私人蓝牙Gatt mGatt;//这用于允许 GUI 片段订阅状态更改通知.公共静态类 StateObservable 扩展了 Observable{私有无效notifyChanged(){设置更改();通知观察者();}};//当逻辑状态改变时,调用State.notifyObservers(this).public final StateObservable State = new StateObservable();公共连接活动逻辑(){}@覆盖public void onCreate(Bundle savedInstanceState){super.onCreate(savedInstanceState);//告诉框架尽量保留这个片段//在配置更改期间.setRetainInstance(true);//实际上设置它以响应 ACTION_PAIRING_REQUEST.final IntentFilter pairingRequestFilter = new IntentFilter(BluetoothDevice.ACTION_PAIRING_REQUEST);pairingRequestFilter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY - 1);getActivity().getApplicationContext().registerReceiver(mPairingRequestRecevier,pairingRequestFilter);//更新界面.状态.notifyChanged();//请注意,我们实际上并不需要请求权限 - 所有应用都获得 BLUETOOTH 和 BLUETOOTH_ADMIN 权限.//LOCATION_COARSE 仅用于我不需要的扫描(MAC 是硬编码的).//连接到设备.连接盖特();}@覆盖公共无效 onDestroy(){super.onDestroy();//如果我们仍然连接,则断开与设备的连接.断开连接();//取消注册广播接收器.getActivity().getApplicationContext().unregisterReceiver(mPairingRequestRecevier);}//UI 用于显示连接进度的状态.公共连接状态 getConnectionState(){返回 mState;}//内部状态机.公共枚举连接状态{闲置的,CONNECT_GATT,发现_服务,READ_CHARACTERISTIC,失败的,成功,}private ConnectionState mState = ConnectionState.IDLE;//创建此片段时,会为其提供 MAC 地址和 PIN 以进行连接.公共字节[] macAddress(){返回 getArguments().getByteArray("mac");}公共 int pinCode(){返回 getArguments().getInt("pin", -1);}//启动连接过程.私有无效connectGatt(){//如果我们已经连接,则断开连接.断开连接();//更新状态.mState = ConnectionState.CONNECT_GATT;状态.notifyChanged();BluetoothDevice 设备 = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(macAddress());//连接!mGatt = device.connectGatt(getActivity(), false, mBleCallback);}private void disconnectGatt(){如果 (mGatt != null){mGatt.disconnect();mGatt.close();mGatt = 空;}}//参见 https://android.googlesource.com/platform/external/bluetooth/bluedroid/+/master/stack/include/gatt_api.h私有静态最终 int GATT_ERROR = 0x85;私有静态最终 int GATT_AUTH_FAIL = 0x89;私人 android.bluetooth.BluetoothGattCallback mBleCallback = 新的 BluetoothGattCallback(){@覆盖public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState){super.onConnectionStateChange(gatt, status, newState);开关(新状态){案例 BluetoothProfile.STATE_CONNECTED://连接到设备.尝试发现服务.如果 (gatt.discoverServices()){//更新状态.mState = ConnectionState.DISCOVER_SERVICES;状态.notifyChanged();}别的{//由于某种原因无法发现服务.失败.断开连接();mState = ConnectionState.FAILED;状态.notifyChanged();}休息;案例 BluetoothProfile.STATE_DISCONNECTED://如果我们尝试在绑定时发现服务,它似乎断开连接.//我们需要脱粘和再粘...开关(mState){案例空闲://在这种情况下什么都不做.休息;案例 CONNECT_GATT://如果绑定信息不正确,就会发生这种情况.删除它并重新连接.deleteBondInformation(gatt.getDevice());连接盖特();休息;案例发现_服务://如果绑定信息不正确,也会发生这种情况.删除它并重新连接.deleteBondInformation(gatt.getDevice());连接盖特();休息;案例READ_CHARACTERISTIC://读取特征时断开连接.可能只是链接失败.gatt.close();mState = ConnectionState.FAILED;状态.notifyChanged();休息;案例失败:案例成功://正常断开.休息;}休息;}}@覆盖public void onServicesDiscovered(BluetoothGatt gatt, int status){super.onServicesDiscovered(gatt, status);//服务已被发现.现在我尝试读取需要中间人保护的特征.//这会触发配对和绑定.BluetoothGattService nameService = gatt.getService(UUIDs.NAME_SERVICE);如果(名称服务 == 空){//无法提供服务.断开连接();mState = ConnectionState.FAILED;状态.notifyChanged();返回;}BluetoothGattCharacteristic 特性 = nameService.getCharacteristic(UUIDs.NAME_CHARACTERISTIC);如果(特征==空){//未找到特征.断开连接();mState = ConnectionState.FAILED;状态.notifyChanged();返回;}//读取特征.gatt.readCharacteristic(特征);mState = ConnectionState.READ_CHARACTERISTIC;状态.notifyChanged();}@覆盖public void onCharacteristicRead(BluetoothGatt gatt,BluetoothGattCharacteristic 特征,int 状态){super.onCharacteristicRead(gatt,特性,状态);如果(状态 == BluetoothGatt.GATT_SUCCESS){//特征读取.检查它是正确的.如果 (!UUIDs.NAME_CHARACTERISTIC.equals(characteristic.getUuid())){//读取错误的特征.这不应该发生.断开连接();mState = ConnectionState.FAILED;状态.notifyChanged();返回;}//获取名称(我正在读取的特征仅包含设备名称).字节 [] 值 = 特性.getValue();如果(值 == 空){//唔...}断开连接();mState = ConnectionState.SUCCEEDED;状态.notifyChanged();//成功!将其保存到数据库或其他任何...}否则如果(状态 == BluetoothGatt.GATT_INSUFFICIENT_AUTHENTICATION){//这是棘手的部分如果 (gatt.getDevice().getBondState() == BluetoothDevice.BOND_NONE){//需要绑定.//应该调用广播接收器.}别的{//?}}否则如果(状态 == GATT_AUTH_FAIL){//这可能是因为用户忽略配对请求通知的时间太长.//或者大概是他们输入了错误的 PIN 码.断开连接();mState = ConnectionState.FAILED;状态.notifyChanged();}否则如果(状态 == GATT_ERROR){//我认为如果债券信息错误会发生这种情况,但现在我不确定.断开连接();mState = ConnectionState.FAILED;状态.notifyChanged();}别的{//那真是怪了.断开连接();mState = ConnectionState.FAILED;状态.notifyChanged();}}};private final BroadcastReceiver mPairingRequestRecevier = new BroadcastReceiver(){@覆盖public void onReceive(上下文上下文,意图意图){如果 (BluetoothDevice.ACTION_PAIRING_REQUEST.equals(intent.getAction())){最终的 BluetoothDevice 设备 = 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();}别的{L.w("意外的配对类型:" + type);}}}};public static void deleteBondInformation(BluetoothDevice device){尝试{//FFS Google,只需取消隐藏该方法.Method m = device.getClass().getMethod("removeBond", (Class[]) null);m.invoke(device, (Object[]) null);}捕获(例外 e){L.e(e.getMessage());}}}

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.

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.

My understanding is as follows.

  1. You connect to the device and discover its services.
  2. You try to read a 'protected' characteristic.
  3. The device returns an authentication error.
  4. Android somehow initiates pairing and you tell it the PIN.
  5. You can now read the characteristic.

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:

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

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?

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

解决方案

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.

Edit: Success!

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.

Here's the procedure.

  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.

Here is the 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 4.4+ 上的 BLE 设备配对的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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