Общение между iOS и Android с Bluetooth LE

У меня есть рабочее приложение, использующее CoreBluetooth для связи между iPad (центральным) и iPhone (периферийным). У меня есть одна служба, которая имеет две характеристики. У меня есть Nexus 7, на котором установлена ​​последняя версия Android 4.3 с поддержкой BTLE. Android немного опоздает, чтобы подскочить на BTW, но похоже, что они приближаются к нему аналогично тому, как работает iOS, где первоначально они поддерживают только центральную роль, когда периферийный режим выходит в более поздней версии. Я могу загрузить образец приложения Android BTLE и просматривать соседние периферийные устройства. С моей iPhone-рекламой в качестве периферии я могу увидеть значение из CBAdvertiseDataLocalNameKey в списке ближайших периферийных устройств на стороне Android. Я могу подключиться к iPhone, и символ Bluetooth превращается из светло-серого в черный, когда соединение выполнено. Соединение всегда длится ровно 10 секунд, а затем отключается. На стороне Android я должен увидеть список доступных сервисов и характеристик сразу после подключения. Я доказал, что код Android настроен правильно, потому что я могу подключить его к оборудованию TI CC2541DK-SENSOR, которое у меня есть, и все службы и характеристики указаны при подключении к нему.

Я провел последние несколько дней, чтобы устранить проблему без успеха. Проблема в том, что я не могу определить, какое устройство испытывает ошибку и, таким образом, вызывает отключение. Не существует обратных вызовов из CBPeripheralManagerDelegate во время фазы подключения или фазы обнаружения службы, поэтому я не знаю, в какой момент произошла ошибка (если ошибка на стороне iOS). На стороне Android вызывается метод, чтобы инициировать обнаружение службы, однако их обратный вызов "onServicesDiscovered" никогда не вызывается, что вызывает недоумение. Есть ли способ, которым я могу вникнуть в мужество связи BTLE на стороне iOS, чтобы узнать, что происходит и определить, какая ошибка происходит?

Ответ 1

Я уже прошел через это, по крайней мере, на одну неделю, имея эту же проблему. Я уже задал вопрос здесь, и я уже ответил сам. Основная проблема - проблема Android BUG. Он отправляет не разрешенную команду на фиксированный канал L2CAP.

Но когда Android общается с обычными периферийными устройствами BLE, он работает очень хорошо. На самом деле образец BLE работает как шарм. Проблема в том, что когда вы работаете с устройством iOS, например: сразу после установления соединения они начинают согласовывать свои параметры подключения (эта фаза не происходит с обычным периферийным устройством BLE), и именно тогда возникает проблема. Android отправляет плохую команду iOS, iOS отключает соединение. Это в основном, как это работает.

Некоторые проблемы уже были сообщены Google, и один из них уже принят, и я надеюсь, что они начнут работать над этим в ближайшее время.

К сожалению, что вы можете сделать, это подождать до следующего выпуска Android. Во всяком случае, я настоятельно рекомендую вам посмотреть мой отчет о проблеме со всеми моими тестовыми документами, если вы хотите осветить эту проблему.

Здесь ссылка: https://code.google.com/p/android/issues/detail?id=58725

Ответ 2

Я написал простой рабочий пример, довольно простой и включил его в Gitub с открытым исходным кодом: https://github.com/GitGarage. Пока он протестирован только с Android Nexus 9 и iPhone 5, но я полагаю, что он также будет работать с Nexus 6 и различными типами iPhone. Пока что он настроен явно для связи между одним Android и одним iPhone, но я полагаю, что он способен делать гораздо больше.

Вот основные методы...

DROID SIDE - отправка в iOS:

private void sendMessage() {
    Thread thread = new Thread(new Runnable() {
        @Override
        public void run() {
            if (mBTAdapter == null) {
                return;
            }
            if (mBTAdvertiser == null) {
                mBTAdvertiser = mBTAdapter.getBluetoothLeAdvertiser();
            }
               // get the full message from the UI
            String textMessage = mEditText.getText().toString(); 
            if (textMessage.length() > 0)
            {
                   // add 'Android' as the user name
                String message = "Android: " + textMessage; 

                while (message.length() > 0) {
                    String subMessage;
                    if(message.length() > 8)
                    {    // add dash to unfinished messages
                        subMessage = message.substring(0,8) + "-"; 
                        message = message.substring(8);
                        for (int i = 0; i < 20; i++) // twenty times (better safe than sorry) send this part of the message. duplicate parts will be ignored
                        {
                            AdvertiseData ad = BleUtil.makeAdvertiseData(subMessage);
                            mBTAdvertiser.startAdvertising(BleUtil.createAdvSettings(true, 100), ad, mAdvCallback);
                            mBTAdvertiser.stopAdvertising(mAdvCallback);
                        }
                    }
                    else
                    {  // otherwise, send the last part
                        subMessage = message;
                        message = "";
                        for (int i = 0; i < 5; i++)
                        {
                            AdvertiseData ad = BleUtil.makeAdvertiseData(subMessage);
                            mBTAdvertiser.startAdvertising(
                                    BleUtil.createAdvSettings(true, 40), ad,
                                    mAdvCallback);
                            mBTAdvertiser.stopAdvertising(mAdvCallback);
                        }
                    }
                }
                threadHandler.post(updateRunnable);
            }
        }
    });
    thread.start();
}

DROID SIDE - получение от iOS:

@Override
public void onLeScan(final BluetoothDevice newDevice, final int newRssi,
                     final byte[] newScanRecord) {

    int startByte = 0;
    String hex = asHex(newScanRecord).substring(0,29);
       // check five times, startByte was used for something else before
    while (startByte <= 5) {
       // check if this is a repeat message
        if (!Arrays.asList(used).contains(hex)) {
            used[ui] = hex;

            String message = new String(newScanRecord);
            String firstChar = message.substring(5, 6);
            Pattern pattern = Pattern.compile("[ [email protected]#$%^&*()_+{}|:\"<>?`\\-=;',\\./\\[\\]\\\\]", Pattern.DOTALL);
               // if the message is comprised of standard characters...
            Matcher matcher = pattern.matcher(firstChar);
            if (firstChar.equals("L"))
            {
                firstChar = message.substring(6, 7);
                pattern = Pattern.compile("[ [email protected]#$%^&*()_+{}|:\"<>?`\\-=;',\\./\\[\\]\\\\]", Pattern.DOTALL);
                matcher = pattern.matcher(firstChar);
            }

            if(matcher.matches())
            {
                TextView textViewToChange = (TextView) findViewById(R.id.textView);
                String oldText = textViewToChange.getText().toString();
                int len = 0;
                String subMessage = "";
                   // add this portion to our final message
                while (matcher.matches())  
                {
                    subMessage = message.substring(5, 6+len);
                    matcher = pattern.matcher(message.substring(5+len, 6+len));
                    len++;
                }
                subMessage = subMessage.substring(0,subMessage.length()-1);

                Log.e("Address",newDevice.getAddress());
                Log.e("Data",asHex(newScanRecord));
                boolean enter = subMessage.length() == 16;
                enter = enter && !subMessage.substring(15).equals("-");
                enter = enter || subMessage.length() < 16;
                textViewToChange.setText(oldText + subMessage.substring(0, subMessage.length() - 1) + (enter ? "\n" : ""));
                ui = ui == 2 ? -1 : ui;
                ui++;

                Log.e("String", subMessage);
            }
            break;
        }
        startByte++;
    }
}

iOS SIDE - отправка на Android:

func startAdvertisingToPeripheral() {
    var allTime:UInt64 = 0;
    if (dataToSend != nil)
    {
        datastring = NSString(data:dataToSend, encoding:NSUTF8StringEncoding) as String
        datastring = "iPhone: " + datastring
        if (datastring.length > 15)
        {
            for (var i:Double = 0; i < Double(datastring.length)/15.000; i++)
            {
                let delay = i/10.000 * Double(NSEC_PER_SEC)
                let time = dispatch_time(DISPATCH_TIME_NOW, Int64(delay))
                allTime = time
                dispatch_after(time, dispatch_get_main_queue(), { () -> Void in self.sendPart() });
            }
        }
        else
        {
            var messageUUID = StringToUUID(datastring)
            if !peripheralManager.isAdvertising {
                peripheralManager.startAdvertising([CBAdvertisementDataServiceUUIDsKey: [CBUUID(string: messageUUID)]])
            }
        }
    }
}

iOS SIDE - получение с Android:

func centralManager(central: CBCentralManager!, didDiscoverPeripheral peripheral: CBPeripheral!, advertisementData: [NSObject : AnyObject]!, RSSI: NSNumber!) {

    delegate?.didDiscoverPeripheral(peripheral)
    var splitUp = split("\(advertisementData)") {$0 == "\n"}
    if (splitUp.count > 1)
    {
        var chop = splitUp[1]
        chop = chop[0...chop.length-2]
        var chopSplit = split("\(chop)") {$0 == "\""}

        if !(chopSplit.count > 1 && chopSplit[1] == "Device Information")
        {
            var hexString = chop[4...7] + chop[12...19] + chop[21...26]
            var datas = hexString.dataFromHexadecimalString()
            var string = NSString(data: datas!, encoding: NSUTF8StringEncoding) as String
            if (!contains(usedList,string))
            {
                usedList.append(string)
                if (string.length == 9 && string[string.length-1...string.length-1] == "-")
                {
                    finalString = finalString + string[0...string.length-2]
                }
                else
                {
                    lastString = finalString + string + "\n"
                    println(lastString)
                    finalString = ""
                    usedList = newList
                    usedList.append(string)
                }
            }
        }
    }
}

Ответ 3

Я хотел бы добавить немного информации в эту ветку в рамках нашей RnD по теме BLE между кроссплатформенными.

Периферийный режим работает без проблем с Xiomi Mi A1 (версия ОС Oreo, Android 8.0).

Вот несколько наблюдений за пропускной способностью, которые мы обнаружили во время нашей RnD на iPhone 8 и Xiomi Mi A1, но она все еще должна созревать с другими пользовательскими ОС Android, используемыми в последней версии Samsung S8. Приведенные ниже данные основаны на write_with_response.

  1. iPhone 8 (BLE 5.0) в качестве центрального рабочего стола и Linux (Ubuntu 16.04 с ключом BLE 4.0): MTU = 2048: пропускная способность - 2,5 килобайта в секунду.

  2. iPhone 8 (BLE 5.0) в качестве центрального устройства и ОС Android с BLE версии 4.2 в качестве периферийного устройства (Xiomi Mi A1): MTU = 180: пропускная способность - 2,5 килобайта в секунду.

  3. iPhone 8 (BLE 5.0) в качестве центрального устройства и iPhone 7 plus (BLE 4.2) в качестве периферийного устройства: MTU = 512: пропускная способность - 7,1 килобайта в секунду.

  4. iPhone 8 (BLE 5.0) в качестве центрального устройства и Samsung S8 (BLE 5.0) в качестве периферийного устройства: Samsung S8 не работал в качестве периферийного устройства

  5. iPhone 8 (BLE 5.0) в качестве центрального и iPhone 8 plus (BLE 5.0) в качестве периферийного устройства: MTU = 512: пропускная способность - 15,5 килобайт в секунду.

Ответ 4

Я делаю что-то подобное с центральной частью Android и периферией iOS. Я обнаружил, что они отключится, если ничего не подписывается ни на одну из периферийных служб.

Не забудьте обновить дескриптор при подписке, иначе он фактически ничего не делает (т.е. вызовите метод делегата на стороне iOS).

public void setCharacteristicNotification(BluetoothGattCharacteristic characteristic, boolean enabled) {
    if (mBluetoothAdapter == null || mBluetoothGatt == null) {
        Log.v(TAG, "BluetoothAdapter not initialized");
        return;
    }

    UUID uuid = UUID.fromString("00002902-0000-1000-8000-00805f9b34fb");    // UUID for client config desc
    BluetoothGattDescriptor descriptor = characteristic.getDescriptor(uuid);
    descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
    mBluetoothGatt.writeDescriptor(descriptor);

    mBluetoothGatt.setCharacteristicNotification(characteristic, enabled);
}

Можно также заметить, что я даже не видел, чтобы устройство iOS выполняло обычное сканирование BLE на Android-устройстве (startLeScan), но запуск BT Classic-сканирования с широковещательным приемником решил проблему (startDiscovery).

Ответ 5

Я просто хотел поделиться своими знаниями об этом, когда я занимался этим некоторое время назад, и я ушел, поскольку Google не поддерживает. Вышеупомянутый код, которого я очень благодарю, не работает. Вы можете закодировать в разумные сроки iOS для iOS или Android для приложения Android bluetooth le, но проблема возникает, когда вы пытаетесь установить связь между iOS и Android. Существует хорошо документированная проблема google (https://code.google.com/p/android/issues/detail?can=2&start=0&num=100&q=&colspec=ID%20Type%20Status%20Owner%20Summary%20Stars&groupby=&sort=&id=58725) Я сотрудничал, но google didnt произносил вообще, и кажется, что они закрыли проблему, и ничего не изменилось в android M, поскольку я изучал код и не вижу никаких дальнейших различий. Проблема возникает, когда Android пытается подключиться и конкретно в предложении "if else"; этот код в основном отвергает передачу и сокращает связь, поэтому он не работает. На данный момент для этого нет решения. Вы можете использовать прямое решение WiFi, но это ограничение, и есть дополнительные проблемы. Проблема не существует, если вы хотите реализовать BLE с внешним оборудованием (малина, датчики и т.д.), Но она не работает между iOS и Android. Технология абсолютно одинакова на обеих платформах, но она не очень хорошо реализована в Android или предназначена для встроенного ловушки Google, чтобы не открывать призрак для связи между обеими платформами.

Ответ 6

Возможно, немного задержка, но, возможно, ваша боль может быть немного облегчена;)

Мы много экспериментировали с кросс-платформенными соединениями BLE (iOS ↔ Android) и узнали, что по-прежнему существует много несовместимостей и проблем со связью.

Если ваш прецедент связан с функцией, и вам нужен только базовый обмен данными, я бы предложил посмотреть на Frameworks и библиотеки, которые могут обеспечить для вас кросс-платформенную связь, без необходимости создавать ее с нуля.

Например: http://p2pkit.io или рядом с Google

Отказ от ответственности: я работаю на Uepaa, разрабатывая p2pkit.io для Android и iOS.