0

I have 2 apps: desktop on Flutter and mobile Java on Android. What I'm tryin to do is to connect to the Android app's GattServer and set its characteristic to notify me when its value change through Flutter desktop app, using universal_ble package.

Everything works fine except the notification part, cuz when I try to set it the package throws PlatformException "failed to update notification".

I'm not sure that I do everything correct since I'm new to BLE stuff.

The dart code of setting notify on connected device

  Future<void> test() async {
    var devs = await UniversalBle.getSystemDevices();
    if(devs.isEmpty) return;
    
    print("Got devices");
    var dev = devs.first;
    String id = dev.deviceId;

    var servs = await UniversalBle.discoverServices(id);

    var serv = servs.firstWhere((e) => e.uuid == "5ca0284d-395d-3e6b-96d5-9ce24184496e");
    print("Service: ${serv.uuid}");

    var char = servs.firstWhere((e) => e.uuid == serv.uuid)
      .characteristics.firstWhere((e) => e.uuid == "26b24c11-0438-3f72-8e76-41296786f4a7");

    print("Characteristic: ${char.uuid}");

    // The problem part goes here
    try {
      await UniversalBle.setNotifiable(id, serv.uuid, char.uuid, BleInputProperty.notification);
    }
    catch(e) {
      if(context.mounted) SeekDialogs.showMessageBox(context, "ERROR!", e.toString());
      return;
    }

    var data = await UniversalBle.readValue(id, serv.uuid, char.uuid);
    print(String.fromCharCodes(data));

    var byteData = Uint8List(4)
      ..buffer.asInt32List()[0] = 250;

    await UniversalBle.writeValue(
      id, 
      serv.uuid, 
      char.uuid, 
      byteData,
      BleOutputProperty.withResponse
    );
 
    data = await UniversalBle.readValue(id, serv.uuid, char.uuid);
    print(String.fromCharCodes(data));

    print("Success!");
  }

The Java BLE Manager class to provide GattServer

import static androidx.core.content.ContextCompat.getSystemService;

import android.Manifest;
import android.annotation.SuppressLint;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCharacteristic;
import android.bluetooth.BluetoothGattDescriptor;
import android.bluetooth.BluetoothGattServer;
import android.bluetooth.BluetoothGattServerCallback;
import android.bluetooth.BluetoothGattService;
import android.bluetooth.BluetoothManager;
import android.bluetooth.le.AdvertiseCallback;
import android.bluetooth.le.AdvertiseData;
import android.bluetooth.le.AdvertiseSettings;
import android.bluetooth.le.BluetoothLeAdvertiser;
import android.content.Context;
import android.content.pm.PackageManager;
import android.util.Log;

import androidx.annotation.NonNull;
import androidx.core.app.ActivityCompat;

import java.util.Arrays;
import java.util.List;
import java.util.UUID;

@SuppressLint("MissingPermission")
public class BleManager {
    public BleManager(Context context) {
        bluetoothManager = getSystemService(context, BluetoothManager.class);
        assert bluetoothManager != null;

        bluetoothAdapter = bluetoothManager.getAdapter();
        bluetoothAdvertiser = bluetoothAdapter.getBluetoothLeAdvertiser();
        initialize(context);
    }

    BluetoothManager bluetoothManager;
    BluetoothAdapter bluetoothAdapter;
    BluetoothLeAdvertiser bluetoothAdvertiser;
    BluetoothGattServer bluetoothGattServer;
    private final String TAG = "BLE Manager";


    private void initialize(Context context) {
        // Modify server callbacks here
        if (ActivityCompat.checkSelfPermission(context, Manifest.permission.BLUETOOTH_CONNECT) != PackageManager.PERMISSION_GRANTED) {
            Log.i(TAG, "No permisiions to init");
            return;
        }
        bluetoothGattServer = bluetoothManager.openGattServer(context, new BluetoothGattServerCallback() {
            @Override
            public void onConnectionStateChange(BluetoothDevice device, int status, int newState) {
                Log.i(TAG, "CONNECTION STATUS CHANGED: " + newState + " for device " + device.getAddress());
            }

            @Override
            public void onServiceAdded(int status, BluetoothGattService service) {
                super.onServiceAdded(status, service);
                Log.i(TAG, "Service add status: " + status);
            }

            @Override
            public void onCharacteristicReadRequest(BluetoothDevice device, int requestId, int offset, BluetoothGattCharacteristic characteristic) {
                if(characteristic == null) {
                    Log.i(TAG, "Characteristic not found to READ!");

                    bluetoothGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_REQUEST_NOT_SUPPORTED, offset, null);
                    return;
                }

                Log.i(TAG, "Reading characteristic: " + characteristic.getUuid());

                bluetoothGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, offset, characteristic.getValue());
            }

            @Override
            public void onCharacteristicWriteRequest(BluetoothDevice device, int requestId, BluetoothGattCharacteristic characteristic, boolean preparedWrite, boolean responseNeeded, int offset, byte[] value) {
                if(characteristic == null) {
                    Log.i(TAG, "Characteristic not found to WRITE!");

                    bluetoothGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_REQUEST_NOT_SUPPORTED, offset, null);
                    return;
                }

                Log.i(TAG, "Writing characteristic: " + characteristic.getUuid());

                try {
                    DeviceCharacteristic[] chars = new DeviceData().getValues();
                    for(DeviceCharacteristic chr : chars) {
                        if(chr.get_uuid().equals(characteristic.getUuid())) {
                            chr.setValue(value);
                            break;
                        }
                    }
                } catch (IllegalAccessException e) {
                    throw new RuntimeException(e);
                }

                if (responseNeeded) {
                    bluetoothGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, offset, value);
                }
//                bluetoothGattServer.notifyCharacteristicChanged(device, characteristic, true);
            }

            @Override
            public void onDescriptorWriteRequest(BluetoothDevice device, int requestId, BluetoothGattDescriptor descriptor, boolean preparedWrite, boolean responseNeeded, int offset, byte[] value) {
                if(descriptor == null) {
                    Log.i(TAG, "Got empty desriptor in WRITE request");
                    return;
                }

                bluetoothGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, offset, value);
            }

            @Override
            public void onNotificationSent(BluetoothDevice device, int status) {
                Log.i(TAG, "Sending notify to " + device.getAddress() + " with status " + status);
            }
        });

        initServices();

        AdvertiseSettings settings = new AdvertiseSettings.Builder()
                .setAdvertiseMode(AdvertiseSettings.ADVERTISE_MODE_LOW_LATENCY)
                .setConnectable(true)
                .setTimeout(0)
                .setTxPowerLevel(AdvertiseSettings.ADVERTISE_TX_POWER_HIGH)
                .build();

        AdvertiseData advertiseData = new AdvertiseData.Builder()
                .setIncludeDeviceName(true)
                .build();

        AdvertiseCallback advertiseCallback = new AdvertiseCallback() {
            @Override
            public void onStartSuccess(AdvertiseSettings settingsInEffect) {
                Log.i(TAG, "BLUETOOTH INITED");
            }

            @Override
            public void onStartFailure(int errorCode) {
                Log.i(TAG, "BLUETOOTH INIT FAILED");
            }
        };

        bluetoothAdvertiser.startAdvertising(settings, advertiseData, advertiseCallback);
    }

    private void initServices() {
        // Modify services here
        List<BluetoothGattService> servs = Arrays.asList(
                createService(
                        UUID.fromString("5ca0284d-395d-3e6b-96d5-9ce24184496e"),
                        Arrays.asList(
                                DeviceData.height,
                                DeviceData.compass,
                                DeviceData.cameraAngle
                        )
                )
        );

        for(BluetoothGattService serv : servs) {
            bluetoothGattServer.addService(serv);
        }
    }

    @NonNull
    private BluetoothGattService createService(UUID serviceId, @NonNull List<DeviceCharacteristic> characteristics) {
        BluetoothGattService gattService = new BluetoothGattService(serviceId, BluetoothGattService.SERVICE_TYPE_PRIMARY);

        for(DeviceCharacteristic airshipData : characteristics) {
            airshipData.setBleData(bluetoothGattServer, bluetoothManager);
            BluetoothGattCharacteristic characteristic = airshipData.getCharacteristic();
            boolean added = gattService.addCharacteristic(characteristic);
            Log.i(TAG, "Characteristic " + characteristic.getUuid() + " " + (added ? "added" : "not added"));
        }

        return gattService;
    }

    public void dispose() {
        if (bluetoothGattServer != null) {
            bluetoothGattServer.close();
        }
        if (bluetoothAdvertiser != null) {
            bluetoothAdvertiser.stopAdvertising(
                    new AdvertiseCallback() {
                    @Override
                    public void onStartSuccess(AdvertiseSettings settingsInEffect) {
                        Log.i(TAG, "BLUETOOTH STOPPED");
                    }

                    @Override
                    public void onStartFailure(int errorCode) {
                        Log.i(TAG, "BLUETOOTH STOP FAILED");
                    }
                }
            );
        }
    }
}

The DeviceData class just contains constant characteristics. Example of an constant object:

import android.annotation.SuppressLint;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothGattCharacteristic;
import android.bluetooth.BluetoothGattServer;
import android.bluetooth.BluetoothManager;
import android.util.Log;

import androidx.annotation.NonNull;

import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.List;
import java.util.UUID;

public class DeviceCharacteristic<T> {
    public DeviceCharacteristic(@NonNull UUID charId, @NonNull T initVal) {
        this.TAG = "Characteristic " + charId;

        _characteristic = new BluetoothGattCharacteristic(
                charId,
                BluetoothGattCharacteristic.PROPERTY_READ | BluetoothGattCharacteristic.PROPERTY_WRITE | BluetoothGattCharacteristic.PROPERTY_NOTIFY,
                BluetoothGattCharacteristic.PERMISSION_READ | BluetoothGattCharacteristic.PERMISSION_WRITE
        );

        this._tClass = (Class<T>) initVal.getClass();
        this._uuid = charId;
        setValue(initVal);

        Log.i(TAG, "Inited as " + _tClass.getSimpleName() + " type");
    }

    private final String TAG;

    private final Class<T> _tClass;
    private final BluetoothGattCharacteristic _characteristic;
    private final UUID _uuid;
}

I tried a lot of things like:

• Setting onWriteDescriptor callback to let the app change the notify value

• Checking the sources of the flutter package and trying to fix it by myself. It seems that the Windows package throws error on unpredicted Exception ( catch(...) {} )

• Recoding the connection part with a lot of checks which doesn't help with notify problem

0