web-dev-qa-db-de.com

Kommunikation zwischen iOS und Android mit Bluetooth LE

Ich habe eine funktionierende App, die CoreBluetooth für die Kommunikation zwischen einem iPad (zentral) und einem iPhone (peripher) verwendet. Ich habe einen Dienst, der zwei Eigenschaften hat. Ich habe ein Nexus 7 mit der neuesten Version Android 4.3 mit BTLE-Unterstützung. Android ist etwas spät, um auf den BTLE-Zug zu springen, aber es scheint, dass sie sich ihm nähern ähnlich wie iOS, wo sie anfänglich nur die Funktion einer Zentrale unterstützen, wobei der Peripheriemodus in einer späteren Version verfügbar ist. Ich kann die Beispiel - App Android BTLE) laden und nach Peripheriegeräten in der Nähe suchen iPhone-Werbung als Peripheriegerät Ich kann den Wert von CBAdvertisementDataLocalNameKey in der Liste der Peripheriegeräte in der Nähe auf der Seite Android=) sehen. Ich kann eine Verbindung zum iPhone herstellen, und das Bluetooth-Symbol wechselt von hellgrau zu schwarz, wenn Die Verbindung wird hergestellt. Die Verbindung dauert immer genau 10 Sekunden und wird dann getrennt. Auf der Seite Android) soll eine Liste der verfügbaren Dienste und Eigenschaften sofort nach dem Herstellen der Verbindung angezeigt werden. Ich habe es bewiesen Der Android Code ist korrekt eingerichtet, da ich ihn mit der TI CC2541DK-SENSOR Hardware verbinden kann, die ich habe Alle Dienste und Eigenschaften werden beim Herstellen einer Verbindung aufgelistet.

Ich habe die letzten Tage damit verbracht, das Problem ohne Erfolg zu beheben. Das Problem ist, dass ich nicht feststellen kann, bei welchem ​​Gerät ein Fehler aufgetreten ist und somit die Trennung verursacht wird. Es gibt keine Rückrufe von CBPeripheralManagerDelegate während der Verbindungsphase oder der Diensterkennungsphase, sodass ich keine Ahnung habe, an welchem ​​Punkt ein Fehler auftritt (wenn der Fehler auf der iOS-Seite auftritt). Auf der Seite Android) wird eine Methode aufgerufen, um die Suche nach Diensten einzuleiten, aber ihr Rückruf "onServicesDiscovered" wird nie aufgerufen, was verwirrend ist iOS-Seite, um zu sehen, was los ist und um festzustellen, welcher Fehler auftritt?

44
afrederick

Ich habe dies bereits für mindestens eine Woche mit dem gleichen Problem durchlaufen. Ich habe hier bereits eine Frage gestellt und diese selbst beantwortet. Das Hauptproblem ist ein Android BUG-Problem. Es wird ein unzulässiger Befehl auf einem festen L2CAP-Kanal gesendet.

Aber wenn Android mit normalen peripheren BLE-Geräten kommuniziert, funktioniert es ziemlich gut. Tatsächlich wirkt das BLE-Beispiel wie ein Zauber. Das Problem ist, wenn beispielsweise mit einem iOS-Gerät kommuniziert wird: Sobald die Verbindung hergestellt ist, beginnen sie mit der Aushandlung ihrer Verbindungsparameter (diese Phase tritt bei normalen BLE-Peripheriegeräten nicht auf). In diesem Fall tritt das Problem auf. Android sendet einen ungültigen Befehl an iOS, iOS beendet die Verbindung. Das ist im Grunde, wie es funktioniert

Einige Probleme wurden bereits an Google gemeldet, und eines davon wurde bereits akzeptiert, und ich hoffe, dass sie bald damit beginnen werden, daran zu arbeiten.

Leider können Sie bis zur nächsten Veröffentlichung von Android warten. Wie auch immer, ich empfehle Ihnen dringend, meinen Problembericht mit all meinen Testdokumenten zu lesen, wenn Sie etwas Licht in dieses Problem bringen möchten.

Hier ist der Link: https://code.google.com/p/Android/issues/detail?id=58725

28
edoardotognoni

Ich habe ein einfaches, relativ einfaches Arbeitsbeispiel geschrieben und es als Open-Source-Version in Github integriert: https://github.com/GitGarage . Bisher wurde es nur mit einem Android Nexus 9 und einem iPhone 5s getestet, aber ich gehe davon aus, dass es auch mit einem Nexus 6 und verschiedenen iPhone-Typen funktionieren würde. Bisher ist es explizit auf eingerichtet Kommunikation zwischen einem Android und einem iPhone, aber ich nehme an, es ist möglich, viel mehr zu tun.

Hier sind die wichtigsten Methoden ...

DROID SIDE - Senden an 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 - Empfangen von 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 - Senden an 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 - Empfangen von 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)
                }
            }
        }
    }
}
15
omikes

Ich möchte diesem Thread einige Informationen als Teil unseres RnD on BLE-Themas zwischen plattformübergreifend hinzufügen.

Der Peripheriemodus funktioniert problemlos mit Xiomi Mi A1 (Betriebssystemversion Oreo, Android 8.0).

Hier sind einige Beobachtungen zum Durchsatz, die wir während unseres RnD auf dem iPhone 8 und Xiomi Mi A1 gefunden haben, aber es muss noch mit anderen benutzerdefinierten Android Betriebssystemen ausgereift werden, die im neuesten Samsung S8 verwendet werden. Die folgenden Daten basieren auf write_with_response.

  1. iPhone 8 (BLE 5.0) als zentraler und Linux-Desktop (Ubuntu 16.04 mit BLE-Dongle 4.0): MTU = 2048: Durchsatz - 2,5 Kilobyte pro Sekunde.

  2. iPhone 8 (BLE 5.0) als zentrales und Android Betriebssystem mit BLE-Version 4.2 als Peripheriegerät (Xiomi Mi A1): MTU = 180: Durchsatz - 2,5 Kilobyte pro Sekunde.

  3. iPhone 8 (BLE 5.0) als Central und iPhone 7 plus (BLE 4.2) als Peripheriegerät: MTU = 512: Durchsatz - 7,1 Kilobyte pro Sekunde.

  4. iPhone 8 (BLE 5.0) als Central und Samsung S8 (BLE 5.0) als Peripheriegerät: Samsung S8 funktioniert nicht als Peripheriegerät

  5. iPhone 8 (BLE 5.0) als Central und iPhone 8 plus (BLE 5.0) als Peripheriegerät: MTU = 512: Durchsatz - 15,5 Kilobyte pro Sekunde.

5
Sudhin Philip

Ich mache etwas Ähnliches mit einem zentralen Android und einem iOS-Peripheriegerät. Ich stellte fest, dass die Verbindung getrennt würde, wenn keiner der Peripheriedienste abonniert würde.

Vergessen Sie nicht, den Deskriptor zu aktualisieren, wenn Sie ihn abonnieren, da er sonst nichts bewirkt (d. H. Die Delegate-Methode auf der iOS-Seite aufrufen).

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

Es könnte auch von Bedeutung sein, dass ich auf dem Android Gerät (startLeScan)) nicht einmal feststellen konnte, dass das iOS-Gerät einen normalen BLE-Scan durchführte, aber das Starten eines BT Classic-Scans mit einem Rundfunkempfänger löste Problem (startDiscovery).