web-dev-qa-db-fra.com

Communiquer entre iOS et Android avec Bluetooth LE

J'ai une application qui utilise CoreBluetooth pour communiquer entre un iPad (central) et un iPhone (périphérique). J'ai un service qui a deux caractéristiques. J'ai un Nexus 7 exécutant la dernière Android 4.3 avec prise en charge BTLE. Android est un peu tard pour sauter sur le train en marche BTLE mais il semble qu'ils s'en approchent) de la même manière qu'iOS, où au départ ils ne prennent en charge que le rôle de central avec le mode périphérique dans une version ultérieure. Je peux charger l'exemple Android BTLE app et parcourir les périphériques à proximité. Avec mon Publicité sur iPhone en tant que périphérique Je peux voir la valeur de CBAdvertisementDataLocalNameKey dans la liste des périphériques à proximité du côté Android. Je peux me connecter à l'iPhone et le symbole Bluetooth passe du gris clair au noir lorsque le la connexion est établie. La connexion dure toujours exactement 10 secondes, puis se déconnecte. Du côté Android je suis censé voir une liste des services et caractéristiques disponibles apparaître immédiatement lors de la connexion. J'ai prouvé le code Android est correctement configuré car je peux le connecter au matériel TI CC2541DK-SENSOR que j'ai et tous les services et caractéristiques sont répertoriés lors de la connexion.

J'ai passé les derniers jours à résoudre le problème sans succès. Le problème est que je ne peux pas déterminer quel appareil rencontre une erreur et provoque ainsi la déconnexion. Il n'y a aucun rappel de CBPeripheralManagerDelegate pendant la phase de connexion ou la phase de découverte de service, donc je n'ai aucune idée du moment où une erreur se produit (si l'erreur est du côté iOS). Du côté Android, une méthode est appelée pour lancer la découverte de service, mais leur rappel "onServicesDiscovered" n'est jamais appelé, ce qui est perplexe. Existe-t-il un moyen de creuser dans les entrailles de la communication BTLE sur le Côté iOS pour voir ce qui se passe et déterminer quelle erreur se produit?

44
afrederick

J'ai déjà vécu cela pendant au moins une semaine avec ce même problème. J'ai déjà posé une question ici et j'ai déjà répondu par moi-même. Le problème principal est un Android BUG issue. Il envoie une commande non autorisée sur un canal L2CAP fixe.

Mais lorsque Android communique avec des périphériques BLE normaux, cela fonctionne plutôt bien. En fait, l'exemple BLE fonctionne comme un charme. Le problème est quand communique avec un périphérique iOS par exemple: Juste une fois la connexion établie, ils commencent à négocier leurs paramètres de connexion (cette phase ne se produit pas avec le périphérique BLE normal), et c'est à ce moment que le problème survient. Android envoie une mauvaise commande à iOS , iOS abandonne la connexion. Voilà comment cela fonctionne

Certains problèmes ont déjà été signalés à Google, et l'un d'entre eux a déjà été accepté et j'espère qu'ils commenceront à y travailler bientôt.

Malheureusement, ce que vous pouvez faire, c'est d'attendre la prochaine Android. Quoi qu'il en soit, je vous suggère fortement de consulter mon rapport de problème avec tous mes documents de test si vous voulez faire un peu de lumière sur ce problème.

Voici le lien: https://code.google.com/p/Android/issues/detail?id=58725

28
edoardotognoni

J'ai écrit un exemple de travail simple, relativement simple, et je l'ai inclus open-source sur Github: https://github.com/GitGarage . Jusqu'à présent, il n'a été testé qu'avec un Android Nexus 9 et un iPhone 5s, mais je suppose qu'il fonctionnerait également avec un Nexus 6 et divers types d'iPhone. Jusqu'à présent, il est configuré explicitement pour communiquer entre un Android et un iPhone, mais je suppose qu'il est possible de faire beaucoup plus.

Voici les principales méthodes ...

DROID SIDE - Envoi vers 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 - Réception depuis 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("[ a-zA-Z0-9~!@#$%^&*()_+{}|:\"<>?`\\-=;',\\./\\[\\]\\\\]", 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("[ a-zA-Z0-9~!@#$%^&*()_+{}|:\"<>?`\\-=;',\\./\\[\\]\\\\]", 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 - Envoi vers 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 - Réception depuis 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

Je voudrais ajouter quelques informations à ce fil dans le cadre de notre sujet RnD sur BLE entre multiplateformes.

Le mode périphérique fonctionne sans aucun problème avec Xiomi Mi A1 (version du système d'exploitation Oreo, Android 8.0).

Voici quelques observations sur le débit que nous avons trouvées lors de notre RnD sur iPhone 8 et Xiomi Mi A1, mais il doit encore mûrir avec d'autres systèmes d'exploitation personnalisés Android OS utilisé dans le dernier Samsung S8. Les données ci-dessous sont basé sur write_with_response.

  1. iPhone 8 (BLE 5.0) en tant que bureau central et Linux (Ubuntu 16.04 avec dongle BLE 4.0): MTU = 2048: débit - 2,5 kilo-octets par seconde.

  2. iPhone 8 (BLE 5.0) en tant que central et Android OS avec BLE version 4.2 en tant que périphérique (Xiomi Mi A1): MTU = 180: débit - 2,5 kilo-octets par seconde.

  3. iPhone 8 (BLE 5.0) en tant que central et iPhone 7 plus (BLE 4.2) en tant que périphérique: MTU = 512: débit - 7,1 kilo-octets par seconde.

  4. iPhone 8 (BLE 5.0) en tant que central et Samsung S8 (BLE 5.0) en tant que périphérique: Samsung S8 n'a pas fonctionné en tant que périphérique

  5. iPhone 8 (BLE 5.0) en tant que central et iPhone 8 plus (BLE 5.0) en tant que périphérique: MTU = 512: débit - 15,5 kilo-octets par seconde.

5
Sudhin Philip

Je fais quelque chose de similaire avec un Android central et un périphérique iOS. J'ai trouvé qu'ils se déconnecteraient si rien n'était abonné à l'un des services du périphérique.

N'oubliez pas de mettre à jour le descripteur lorsque vous vous abonnez, il ne fait rien (c'est-à-dire appeler la méthode déléguée du côté 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);
}

Il convient également de noter que je ne pouvais même pas voir l'appareil iOS effectuer un balayage BLE normal sur le Android (startLeScan), mais le démarrage d'un balayage BT Classic avec un récepteur de diffusion a résolu le problème (startDiscovery).

1