Communicating between iOS and Android with Bluetooth LE

后端 未结 6 580
遥遥无期
遥遥无期 2020-11-28 18:15

I\'ve got a working app using CoreBluetooth to communicate between an iPad (central) and iPhone (peripheral). I have one service that has two characteristics. I have a Nexus

相关标签:
6条回答
  • 2020-11-28 18:40

    I am doing something similar with an Android central and an iOS peripheral. I found that they would disconnect if nothing subscribed to any of the peripheral's services.

    Don't forget to update the descriptor when subscribing else it doesn't actually do anything (i.e. call the delegate method on the iOS side).

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

    It might also be of note that I couldn't even see the iOS device doing an normal BLE scan on the Android device (startLeScan), but starting a BT Classic scan with a broadcast receiver solved the problem (startDiscovery).

    0 讨论(0)
  • 2020-11-28 18:44

    I've already gone through this for at least one week having this same issue. I've already asked a question here and I've already answered on my own. The main problem is an Android BUG issue. It's sending a non permitted command on a fixed L2CAP channnel.

    But when Android is communicating with normal peripheral BLE devices, it works pretty well. In fact, the BLE sample works like a charm. The problem is when is comunicating with an iOS device for example: Just after the connection is made, they start negotiating their connection parameters (this phase doesn't happen with normal BLE peripheral), and this is when the problem comes up. Android sends a bad command to iOS, iOS drops the connection. That's basically how it works

    Some issues have been already reported to Google, and one of them have been already accepted and I hope they will start working on it soon.

    Unfortunately, what you can do, is to wait until next Android release. Anyway, I highly suggest you to have a look at my issue report with all my test documents if you want to make some light on this problem.

    Here's the link: https://code.google.com/p/android/issues/detail?id=58725

    0 讨论(0)
  • 2020-11-28 18:50

    I just wanted to share my knowledge on that as I dealt with it some time ago and I quit as there is no support by google. The aforementioned code, what I thank a lot, does not work. You can code in a reasonable time an iOS to iOS or android to android bluetooth le application but the problem comes when you try to communicate between iOS and android. There is a well documented google issue (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) I collaborated but google didnt pronounce at all and it seems they closed the issue and nothing has changed in android M as Ive been looking into the code and can´t see no further differences. The problem comes when Android tries to connect and specifically in a "if else" sentence; this code basically rejects the transmission and cuts the communication so it doesn´t work. At the moment, there is no solution for that. You can do a WiFi direct solution, but it´s a limitation and there are further problems doing that. The problem doesn´t exist if you want to implement BLE with external hardware (raspberry, sensors, etc.,) but it doesn´t work between iOS and android. The technology is quite the same in both platforms but it´s not well implemented in Android or is purpose inserted pitfall by google to not open the spectre to communicate between both platforms.

    0 讨论(0)
  • 2020-11-28 18:52

    I've written a simple working example, well relatively simple, and included it open-source on Github: https://github.com/GitGarage. So far it has only been tested with an Android Nexus 9 and an iPhone 5s, but I presume it would also work with a Nexus 6 and various iPhone types. So far it is set up explicitly to communicate between one Android and one iPhone, but I presume it is tweakable to do much more.

    Here are the key methods...

    DROID SIDE - Sending to 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 - Receiving from 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 - Sending to 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 - Receiving from 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)
                    }
                }
            }
        }
    }
    
    0 讨论(0)
  • 2020-11-28 18:52

    I would like to add few information to this thread as a part of our RnD on BLE topic between cross platform.

    Peripheral mode is working without any issues with Xiomi Mi A1 (OS version Oreo, Android 8.0).

    Here are few observation on throughput that we found during our RnD on iPhone 8 and Xiomi Mi A1 but it still has to get matured with other custom Android OS used in latest Samsung S8. The below data is based on write_with_response.

    1. iPhone 8 (BLE 5.0) as Central and Linux desktop (Ubuntu 16.04 with BLE dongle 4.0): MTU = 2048 : Throughput - 2.5 KiloBytes per sec.

    2. iPhone 8 (BLE 5.0) as Central and Android OS with BLE version 4.2 as Peripheral(Xiomi Mi A1): MTU = 180 : Throughput - 2.5 KiloBytes per sec.

    3. iPhone 8 (BLE 5.0) as Central and iPhone 7 plus (BLE 4.2) as Peripheral : MTU = 512 : Throughput - 7.1 KiloBytes per sec.

    4. iPhone 8 (BLE 5.0) as Central and Samsung S8 (BLE 5.0) as Peripheral : Samsung S8 failed to work as peripheral

    5. iPhone 8 (BLE 5.0) as Central and iPhone 8 plus (BLE 5.0) as Peripheral : MTU = 512 : Throughput - 15.5 KiloBytes per sec.

    0 讨论(0)
  • 2020-11-28 19:02

    Maybe a bit delayed, but perhaps your pain can be relieved slightly ;)

    We have been experimenting a lot with cross platform BLE connections (iOS<-> Android) and learned that there are still many incompatibilities and connection issues.

    If your use case is feature driven and you only need basic data exchange I would suggest to look at Frameworks and Libraries that can achieve cross platform communication for you, without you needing to build it up from scratch.

    For example: http://p2pkit.io or google nearby

    Disclaimer: I work for Uepaa, developing p2pkit.io for Android and iOS.

    0 讨论(0)
提交回复
热议问题