NordicSemiconductor / Android-BLE-Library

A library that makes working with Bluetooth LE on Android a pleasure. Seriously.
BSD 3-Clause "New" or "Revised" License
2.05k stars 420 forks source link

Using device as both central and peripheral with GATT server and client #202

Closed fmeef closed 2 years ago

fmeef commented 4 years ago

I am trying to set up a p2p service over GATT where each device can serve as both the central and the peripheral, with a gatt server and one or more gatt clients.

I am having some issues getting the server to accept clients. I am advertising my service UUID, which shows up when scanning fine. I connect to the discovered device with (in my ConnectionObserver)

mManager = new BluetoothLEManager<>(mContext);
        mManager.setConnectionObserver(this);
        mManager.connect(device)
                .timeout(100000)
                .retry(3,1000)
                .fail((a, b) -> {
                    Log.e(TAG, "failed to connect to client: " + b);
                })
                .done(log -> {
                    Log.v(TAG,
                            "connected to device " + device.getAddress() + " "
                    + log.toString());
                })
                .enqueue();

Connecting results in E/BluetoothLE: failed to connect to client: -5 (timeout)

For the GATT server,

I think I am suppose to create another BleManager instance when onDeviceConnectedToServer is called so I can discover services and connect to the new client, but onDeviceConnectedToServer is not called when a 2nd device attempts to connect.

Since I cannot perform service discovery of the GATT client from the GATT server, onServerReady() in BleManagerGattCallback is not called either. onServerReady() in my ServerObserver class is called on startup though.

Is there anything I am missing, or is this a bug?

philips77 commented 4 years ago

Do you try to connect between 2+ Android devices? Do you start advertising with connectable flag set to true on the one that should act as peripheral?

Correct steps should look like the following:

  1. Define BleServerManager on both devices, and start them. You should get onServerReady() callback.
  2. On one of them start advertising, on another start scanning.
  3. When device is found, connect using the code snippet above.
  4. On the other phone you should get onDeviceConnectedToServer().
fmeef commented 4 years ago

Yes, I am connecting between two android devices. I am advertising with connectable set to true on both devices if that matters. The issue I have is that onDeviceConnectedToServer() is not getting called on the other device

Here is the code I am using for advertising. This is on both devices. I noticed that it still fails when starting the GATT server before starting advertising, as well as after.

AdvertiseSettings settings = new AdvertiseSettings.Builder()
                    .setAdvertiseMode(AdvertiseSettings.ADVERTISE_MODE_BALANCED)
                    .setConnectable(true)
                    .setTimeout(0)
                    .setTxPowerLevel(AdvertiseSettings.ADVERTISE_TX_POWER_LOW)
                    .build();
AdvertiseData addata = new AdvertiseData.Builder()
        .setIncludeDeviceName(false)
        .setIncludeTxPowerLevel(false)
        .addServiceUuid(new ParcelUuid(SERVICE_UUID))
        .build();

mAdvertiseCallback = new AdvertiseCallback() {
    @Override
    public void onStartSuccess(AdvertiseSettings settingsInEffect) {
        super.onStartSuccess(settingsInEffect);
        Log.v(TAG, "successfully started advertise");

        final BluetoothManager bm = (BluetoothManager) mContext.getSystemService(Context.BLUETOOTH_SERVICE);
        mServerObserver.startServer(); //This is the ServerObserver class
    }

    @Override
    public void onStartFailure(int errorCode) {
        super.onStartFailure(errorCode);
        Log.e(TAG, "failed to start advertise");
    }
};

mAdvertiser.startAdvertising(settings, addata, mAdvertiseCallback);
philips77 commented 4 years ago

Could you check with nRF Connect? Enable Gatt server on the phone and start advertising with connectable advertising set. Connect using another phone and check if the advertising phone shows incoming connection. This is the same behavior, although different code. nRF Connect doesn't use this library.

fmeef commented 4 years ago

Connecting with nRF Connect works fine. onDeviceConnectedToServer() is called on my codebase and I can discover/view services and characteristics using the nRF Connect app.

georrge1994 commented 3 years ago

I think I have same problem, but with another error code: GATT, failed to start advertise errorCode = 1 I'm trying to start advisement when server will be ready, but every time got error. I have 2 service (ftms and hrt). In theory I know that adv should be start when server will add services, but on practice it doesn't work. My adv:

 /**
     * Start advertising
     *
     * @param bluetoothLeAdvertiser This class provides a way to perform Bluetooth LE advertise operations
     * @param advertiseCallback Bluetooth LE advertising callbacks, used to deliver advertising operation status
     */
    fun startAdvertising(
        bluetoothLeAdvertiser: BluetoothLeAdvertiser?,
        advertiseCallback: AdvertiseCallback
    ) = bluetoothLeAdvertiser?.startAdvertising(
        getAdvertiseSettings(),
        getAdvertiseData(),
        getScanResponse(),
        advertiseCallback
    )

    /**
     * Get advertise settings
     *
     * @return [AdvertiseSettings]
     */
    private fun getAdvertiseSettings(): AdvertiseSettings? =
        AdvertiseSettings.Builder()
            .setAdvertiseMode(AdvertiseSettings.ADVERTISE_MODE_LOW_LATENCY)
            .setTxPowerLevel(AdvertiseSettings.ADVERTISE_TX_POWER_HIGH)
            .setConnectable(true)
//            .setTimeout(0)
            .build()

    /**
     * Get advertise data
     *
     * @return [AdvertiseData]
     */
    private fun getAdvertiseData(): AdvertiseData = AdvertiseData.Builder()
        .setIncludeDeviceName(true)
        .setIncludeTxPowerLevel(true)
        .addServiceUuid(ParcelUuid(UUID_HEART_RATE_SERVICE_UUID))
        .addServiceUuid(ParcelUuid(UUID_FITNESS_MACHINE_SERVICE_UUID))
        .build()

    /**
     * Get scan response
     *
     * @return [AdvertiseData]
     */
    private fun getScanResponse(): AdvertiseData = AdvertiseData.Builder()
        .setIncludeDeviceName(true)
        .setIncludeTxPowerLevel(true)
        .build()

and start:

   override fun startGattServer() {
          super.startGattServer()
          val referenceName = appContext.getString(
              R.string.gatt_server_name_with_serial,
              bikeSettingsSharedPrefRepository.getBikeSerial()
          )
          if (bluetoothManager.adapter.name != referenceName)
              bluetoothManager.adapter.name = referenceName

          carolBikeServerManager.open()
          carolBikeServerManager.setServerObserver(this@GattServerController)

      }

      override fun onServerReady() {
          log.info("GATT, onServerReady")
          advertiseUseCase.startAdvertising(bluetoothManager.adapter?.bluetoothLeAdvertiser, advertiseCallback)
      }

@philips77 can you please advice me?

georrge1994 commented 3 years ago

Maybe it was wrong, but I resolve this problem by removing name and tx-levels from package. But what we should do if it will not be enough?

philips77 commented 2 years ago

I'm closing the issue due to inactivity.