Search code examples
androidbluetooth-lowenergyaltbeacongatt

Beacons + Bluetooth communication (GATT profile) isn't working fine [BLE]


The problem

I've a peripheral device that is emitting beacons with a specific ID. With my smartphone (that has the central role) is searching the beacon of the peripheral device. When it finds the correct beacon, it begins a bluetooth communication via GATT profile. The first time that the devices are connected, the data is send from one to the other correctly, but the second time (I've a timer to try to connect both devices every 20 seconds), the connection fails I don't know why... I've to kill the app process and reopen it to make it works again.

What I've done

For the beacons, i'm using altbeacon, so the code for beacon detection in central device is this:

@Override
public void onBeaconServiceConnect() {
    beaconManager.setRangeNotifier(new RangeNotifier() {
        @Override
        public void didRangeBeaconsInRegion(Collection<Beacon> beacons, Region region) {
            Log.i(TAG, "Beacon detected!");
            if (beacons.size() > 0 && Utils.getUserData(getApplicationContext(), "id").compareTo("-1") != 0) {
                if(region.getId1().compareTo(Identifier.parse(identifier))==0) {
                    Beacon beacon = beacons.iterator().next();
                    Log.i(TAG, "The first beacon I see is about " + beacon.getDistance() + " meters away. "
                            + region.getId1());
                    String mac = beacon.getBluetoothAddress();
                    Log.i("MAC", "MAC: "+mac);
                    CustomBluetoothManager customBluetoothManager = CustomBluetoothManager.getInstance(getApplicationContext());
                    if(!customBluetoothManager.isScanning()) {
                        customBluetoothManager.scanLeDevice(true, mac);
                    }
                }
            }
        }
    });

    try {
        beaconManager.startRangingBeaconsInRegion(new Region("myRangingUniqueId", Identifier.parse(identifier),
                null, null));
    } catch (RemoteException e) {    }
}

When the beacon is found, then scanLeDevice is called:

public void scanLeDevice(final boolean enable, String mac) {
    target_mac = mac;
    mHandler = new Handler();

    if (enable) {
        // Stops scanning after a pre-defined scan period.
        mHandler.postDelayed(new Runnable() {
            @Override
            public void run() {
                mScanning = false;
                mBluetoothAdapter.stopLeScan(mLeScanCallback);
            }
        }, SCAN_PERIOD);

        mScanning = true;
        mBluetoothAdapter.startLeScan(mLeScanCallback);
    } else {
        mScanning = false;
        mBluetoothAdapter.stopLeScan(mLeScanCallback);
    }
}

and here's the mLeScanCallback code:

 private BluetoothAdapter.LeScanCallback mLeScanCallback =
        new BluetoothAdapter.LeScanCallback() {
            @Override
            public void onLeScan(final BluetoothDevice device, int rssi,
                                 byte[] scanRecord) {
                long epoch = System.currentTimeMillis() / 1000;
                SharedPreferences prefs = context.getSharedPreferences(
                        "epoch", Context.MODE_PRIVATE);
                long epochStored = prefs.getLong("epoch", 0);

                if (device.getAddress().compareTo(MAC) == 0 && after20Seconds) {
                    prefs.edit().putLong("epoch", epoch).apply();
                    Log.d("epoch", Long.toString(epoch));
                    mDeviceAddress = device.getAddress();
                    final Intent gattServiceIntent = new Intent(context, BluetoothLeService.class);
                    context.bindService(gattServiceIntent, mServiceConnection, context.BIND_AUTO_CREATE);
                }
            }
        };

I have had to hardcode the MAC address, because the MAC address that the beacons include in the packets are not the same of the real device MAC (that's from the BLE definition, but I don't know exactly how it works).

Finally, when the data is sent, I close and disconnect the bluetooth channel and I've tried to unbind the service too, but nothing works:

@Override
    public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
        super.onCharacteristicWrite(gatt, characteristic, status);
        disconnect();
        close();
        unbindService(mServiceConnection);
    }

So, with this code, the first time that the devices are connected the data is sent correctly, but after 20 seconds, in the second try, never will establish the communication.

Questions

  1. Any idea of what the problem could be?
  2. How BLE works? In beacons packets, the MAC is fake for security reasons. However, if I get it from beacons scan apps and I hardcode it into the app, the communication works... I think that it shouldn't works.
  3. How can I get the correct device MAC from the beacon packet?
  4. Do you know any sample to use beacons with bluetooth communication?
  5. If I stop the scan through "mBluetoothAdapter.stopLeScan(mLeScanCallback)" method, seems that the scan does not really stops... That's normal? The log is continuosly showing "BluetoothLeScanner: startRegisteration: mLeScanClients = {org.altbeacon.beacon.service.scanner.CycledLeScannerForLollipop $ 4 @ 5445b45 = android.bluetooth.le.BluetoothLeScanner......"

Solution

  • You cannot read the MAC address with Android BLE APIs. As you know, it gets spoofed with a fake MAC that is mapped to the real one by the OS. So this line will not be reliable:

    device.getAddress().compareTo(MAC) == 0

    This may be what is causing connections to fail the second tine. The fact that it work on first launch is surprising, and probably only due to a deterministic implementation of Android's MAC spoofing rotation algorithm. The fact that it does not work the second connect

    You will need find a different way of finding the right device to connect to, perhaps by beacon identifier.

    It is hard to find good sample code for connecting to beacons, because that is not the typical use case. Typically, they are treated as transmit only devices as they are designed. This is why the Android Beacon Library does not provide tools to connect to BLE services -- there is no standard implementation across all beacon manufacturers. That said, your custom approach is reasonable.

    Understand that the Android Beacon Library does its own bluetooth scanning to detect beacons. This is why even though you stop your scanning, you still see scan results in the log. This is coming from the library.

    Full Disclosure: I am lead developer on the Android Beacon Library open source project.