Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

BluetoothGattServer is always disconnecting after 30 seconds

I've been trying to figure out how to get these phones to talk to each other via Bluetooth. I have the android phone set up as a peripheral, and I have my iPhone running the nrf connect app. I am able to advertise and connect to the Android phone from the iPhone, and I am able to subscribe to notifications and see the characteristic updated. The problem is that if I don't send a characteristic notification, after about 7-10 seconds then the connection is lost. I get a callback on the connectionStateChanged callback handler, and I can't figure out what's causing this. I don't think it's the nrf app, because I've connected to other things and it just stays connected forever. Is there something I'm missing?

Here's some code:

private BluetoothManager bluetoothManager;
private BluetoothGattServer gattServer;
private BluetoothGattCharacteristic test_characteristic;
private BluetoothDevice connected_device;


private BluetoothGattServerCallback gattServerCallback = new BluetoothGattServerCallback() {
    @Override
    public void onConnectionStateChange(BluetoothDevice device, int status, int newState) {
        super.onConnectionStateChange(device, status, newState);
        if (newState == 2){ //CONNECTED
            connected_device = device;
        }
        Log.i("chase", "Connection state changed: "+newState);
    }
};

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    BluetoothRegulator.sharedInstance().appContext = getApplicationContext();
    bluetoothManager = (android.bluetooth.BluetoothManager) this.getSystemService(Context.BLUETOOTH_SERVICE);
    BluetoothRegulator.sharedInstance().initialize(bluetoothManager);

    if( ContextCompat.checkSelfPermission(this, android.Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
        ActivityCompat.requestPermissions(this, new String[]{android.Manifest.permission.ACCESS_COARSE_LOCATION}, 1);
    }


    gattServer = bluetoothManager.openGattServer(this, gattServerCallback);
    String uuid_string = "92D9D153-9BE6-43FF-9672-3E2904628B9D";
    BluetoothGattService service = new BluetoothGattService(UUID.fromString(uuid_string), BluetoothGattService.SERVICE_TYPE_PRIMARY);

    uuid_string = "43FF0001-9BE6-43FF-9672-3E2904628B9D";
    test_characteristic = new BluetoothGattCharacteristic(
            UUID.fromString(uuid_string),
            BluetoothGattCharacteristic.PROPERTY_BROADCAST | BluetoothGattCharacteristic.PROPERTY_NOTIFY | BluetoothGattCharacteristic.PROPERTY_READ | BluetoothGattCharacteristic.PROPERTY_WRITE,
            BluetoothGattCharacteristic.PERMISSION_READ | BluetoothGattCharacteristic.PERMISSION_WRITE);
    test_characteristic.setValue( ""+System.currentTimeMillis() );
    service.addCharacteristic(test_characteristic);
    gattServer.addService(service);

    findViewById(R.id.send_data_btn).setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View view) {
            shot_detected_characteristic.setValue( ""+System.currentTimeMillis() );
            gattServer.notifyCharacteristicChanged(connected_device, shot_detected_characteristic, false);
        }
    });

    findViewById(R.id.advertise).setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View view) {
            advertise();
        }
    });

}
private void advertise() {
    BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
    BluetoothLeAdvertiser advertiser = adapter.getBluetoothLeAdvertiser();
    AdvertiseSettings settings = new AdvertiseSettings.Builder()
            .setAdvertiseMode( AdvertiseSettings.ADVERTISE_MODE_LOW_LATENCY )
            .setTxPowerLevel( AdvertiseSettings.ADVERTISE_TX_POWER_HIGH )
            .setConnectable( true )
            .build();
    String uuid_string = "92C9D164-9BE6-43FF-9672-3E2804618B9C";
    ParcelUuid pUuid = new ParcelUuid( UUID.fromString( uuid_string ) );

    AdvertiseData data = new AdvertiseData.Builder()
            .setIncludeDeviceName( true )
            .addServiceData( pUuid, "TEST".getBytes( Charset.forName( "UTF-8" ) ) )
            .build();

    AdvertiseCallback advertisingCallback = new AdvertiseCallback() {
        @Override
        public void onStartSuccess(AdvertiseSettings settingsInEffect) {
            Log.e( "BLE", "Advertising Started");
            super.onStartSuccess(settingsInEffect);
        }

        @Override
        public void onStartFailure(int errorCode) {
            Log.e( "BLE", "Advertising onStartFailure: " + errorCode );
            super.onStartFailure(errorCode);
        }
    };
    advertiser.startAdvertising( settings, data, advertisingCallback );
}

Edit: Actually, I'm noticing that it has nothing to do with being idle. It's actually disconnecting pretty much at the 30 second mark every time.

EDIT: So I added onCharacteristicRead & onCharacteristicWrite callbacks. But that doesn't seem to change anything, and that makes sense to me, because I am not doing anything in them except calling super.onCharacteristicRead() anyway. Also, those aren't going to be called if I am not making reads and writes. And it seems like the Bluetooth should stay connected more than 30 seconds even if there is not a read or write request.

private BluetoothGattServerCallback gattServerCallback = new BluetoothGattServerCallback() {
    @Override
    public void onConnectionStateChange(BluetoothDevice device, int status, int newState) {
        super.onConnectionStateChange(device, status, newState);
        if (newState == 2){
            connected_device = device;
            advertiser.stopAdvertising(new AdvertiseCallback() {});
        }
    }

    @Override
    public void onCharacteristicReadRequest(BluetoothDevice device, int requestId, int offset, BluetoothGattCharacteristic characteristic) {
        super.onCharacteristicReadRequest(device, requestId, offset, characteristic);
    }

    @Override
    public void onCharacteristicWriteRequest(BluetoothDevice device, int requestId, BluetoothGattCharacteristic characteristic, boolean preparedWrite, boolean responseNeeded, int offset, byte[] value) {
        super.onCharacteristicWriteRequest(device, requestId, characteristic, preparedWrite, responseNeeded, offset, value);
    }

};

SOLUTION

So the problem lies in the onCharacteristicReadRequest callback. According to the docs:

An application must call BluetoothGattServer#sendResponse to complete the request.

So once I updated my callback to look like this:

@Override
public void onCharacteristicReadRequest(BluetoothDevice device, int requestId, int offset, BluetoothGattCharacteristic characteristic) {
    super.onCharacteristicReadRequest(device, requestId, offset, characteristic);
    gattServer.sendResponse(device, requestId, 1, offset, characteristic.getValue());
}

and then made a read every few seconds, then it wouldn't time out and disconnect.

like image 550
Chase Roberts Avatar asked Oct 21 '25 05:10

Chase Roberts


1 Answers

Per the ble core specification, for ATT

A transaction not completed within 30 seconds shall time out.

In practice, this usually manifests as a disconnect by one side of the connection.

Some types of GATT requests require responses (i.e Indications, Read Requests, Write Requests). Doesn't look like you are implementing callbacks such as onCharacteristicReadRequest & onCharacteristicWriteRequest in your Gatt server implementation which could lead to this problem.

like image 59
chrisc11 Avatar answered Oct 22 '25 19:10

chrisc11