【问题标题】:Programmatically pairing with a BLE device on Android 4.4+以编程方式与 Android 4.4+ 上的 BLE 设备配对
【发布时间】:2016-10-29 13:41:13
【问题描述】:

有没有人有一个完整的工作示例,说明如何在 Android 4.4 或之后? “以编程方式”是指我告诉 Android PIN - 不会提示用户。

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

我的理解如下。

  1. 您连接到设备并发现其服务。
  2. 您尝试读取“受保护”特征。
  3. 设备返回身份验证错误。
  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 Control Panel 阅读它时,我会收到这样的配对请求通知:

我可以输入此 PIN,然后 MCP 说我已绑定,并且可以读取特征。

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

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

【问题讨论】:

  • Android SDK 是否允许在不通知/要求确认的情况下配对?听起来您可能无法...
  • 是的。它有一个专门用于此的setPin() 方法,除了“配对请求”通知仍然显示之外,我已经让它工作了。
  • 我不确定这是否有帮助,但值得一读stackoverflow.com/questions/17971834/…
  • 是的,我已经看到了。那里也没有答案。 :-/
  • 你能回答我的问题吗?在security.stackexchange.com/questions/179298/…

标签: android bluetooth bluetooth-lowenergy pairing


【解决方案1】:

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

编辑:成功!

我最终求助于阅读BluetoothPairingRequestthe code that sends the pairing request broadcast 的源代码,并意识到我应该拦截ACTION_PAIRING_REQUEST。幸运的是,这是一个有序的意图广播,因此您可以在系统之前拦截它。

这是程序。

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

这里是代码。

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

【讨论】:

  • 嗨!您能否说出 MITM 保护要求的来源:“尝试读取需要加密 MitM 保护的特征”是 GATT 配置文件规范、手机还是您的产品?
  • 在产品上设置。每个 GATT 特征都可以配置为these values 之一。我不确定这与Bluetooth Core Spec 的对应关系如何,但在第 3 卷第 G 部分第 8 节和第 3 卷 C 部分第 5.2.2 节中有一些信息。
  • 好的,我现在从代码中看到了。你真的需要 MITM 保护吗?如果 PIN 是硬编码的,则保护不是很强。禁用 MITM 保护应该会导致 Just Works 配对,然后应该没有 PIN 对话框。
  • 是的,我需要它来防止临时攻击者(即您的典型邻居)。它足够强大。
  • @Timmmm 我无法弄清楚你在UUIDs 中放置了什么,我正在用 null 初始化静态变量。但这会导致错误。请你告诉我一个方法
【解决方案2】:

我也遇到了同样的问题,经过所有研究,我找到了以下解决方案,无需任何人工干预即可与 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...");
        }
    }
};

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

希望这会有所帮助 :-) 如果它适合你,请给出正确的答案。

【讨论】:

  • 这似乎和我的回答一模一样。
  • 我只是简化并添加了它,以免人们混淆。
  • 好吧,你可能应该这么说,并解释更改,例如删除abortBroadcast() 并添加createBond()
猜你喜欢
  • 2015-10-29
  • 1970-01-01
  • 1970-01-01
  • 2012-12-23
  • 1970-01-01
  • 1970-01-01
  • 2016-03-28
  • 2013-02-24
  • 1970-01-01
相关资源
最近更新 更多