RxAndroidBle waiting for response from peripheral when write on it (not long write) in Kotlin

198 Views Asked by At

I am trying to write to a peripheral in Android Kotlin using RxAndroidBle. The application writes to the peripheral and then the peripheral responds if this write request is successful, i.e. According to the evaluation made of the information sent to the peripheral, the peripheral sends a response to the app if it is the expected information, if not the expected information, then the peripheral responds with a different response; In summary, it is a scenario very similar to an HTTP request via POST, information is sent and the server responds with a status if the information meets the requirements. I already managed to connect perfectly and read information from the peripheral in the following way:

override fun connectDeviceToGetInfoHardwareByBle(mac: String): Observable<Resource<HardwareInfoResponse>> {
        val device: RxBleDevice = bleClient.getBleDevice(mac)
        return Observable.defer {
            device.bluetoothDevice.createBond()// it is a blocking function
            device.establishConnection(false) // return Observable<RxBleConnection>
        }
        .delay(5, TimeUnit.SECONDS)
        .flatMapSingle { connection ->
            connection.requestMtu(515)
            .flatMap {
                Single.just(connection)
            }
        }
        .flatMapSingle {
            it.readCharacteristic(UUID.fromString(GET_HARDWARE_INFORMATION_CHARACTERISTIC))
            .map { byteArray ->
                evaluateHardwareInfoResponse(byteArray = byteArray)
            }
        }
        .map {
            Resource.success(data = it)
        }
        .take(1)
        .onErrorReturn {
            Timber.i("Rointe Ble* Error getting ble information. {$it}")
            Resource.error(data = null, message = it.message.toString())
        }
        .doOnError {
            Timber.i("Rointe Ble*","Error getting ble information."+it)
        }
        .subscribeOn(ioScheduler)
        .observeOn(uiScheduler)
    }

As you can see, the MTU is needed by the peripheral, and it answers what I need. After that response, I close that BLE connection and the app does another independent job on the network (HTTP). Then it is required to connect again but this time it is necessary to write JSON information to the peripheral and the device analyzes that JSON and gives some answers that I need as a return; How do I implement a write waiting for a response from the peripheral? Is it necessary to do a long-write for a JSON since I'm assigning MTU on the connection? I'm developing this in Kotlin under the Repository pattern.

The JSON sent is this:

{
"data": {

"id_hardware": "[ID_HARDWARE]",
"product_brand": <value>,
"product_type": <value>,
"product_model": <value>,
"nominal_power": <value>,
"industrialization_process_date": <value>,
"platform_api_path": "[Host_API_REST]",
"platform_streaming_path": "[Host_STREAMING]",
"updates_main_path": "[Host_UPDATES]",
"updates_alternative_path": "[Host_ALTERNATIVE_UPDATES]",
"check_updates_time": <value>,
"check_updates_day": <value>,
"auth_main_path": "[Host_AUTHORIZATION]",
"auth_alternative_path": "[Host_BACKUP_AUTHORIZATION]",
"analytics_path": "[Host_ANALYTICS]",
"idToken": "[ID_TOKEN]",
"refreshToken": "[REFRESH_TOKEN]",
"expiresIn": "3600",
"apiKey": "[API_KEY]",
"factory_wifi_ssid": "[FACTORY_WIFI_SSID]",
"factory_wifi_security_type": "[FACTORY_WIFI_TYPE]",
"factory_wifi_passphrase": "[FACTORY_WIFI_PASS]",
"factory_wifi_dhcp": 1,
"factory_wifi_device_ip": "[IPv4]",
"factory_wifi_subnet_mask": "[SubNetMask_IPv4]",
"factory_wifi_gateway": "[IPv4]"

},
"factory_version": 1,
"crc": ""
}

The peripheral analyzes that JSON and gives me some answers according to the JSON sent.

Now, the way I try to do the write expecting a response is this:

private fun setupNotifications(connection: RxBleConnection): Observable<Observable<ByteArray>> =
            connection.setupNotification(UUID.fromString(SET_FACTORY_SETTINGS_CHARACTERISTIC))

    private fun performWrite(connection: RxBleConnection, notifications: Observable<ByteArray>, data: ByteArray): Observable<ByteArray> {
        return connection.writeCharacteristic(UUID.fromString(SET_FACTORY_SETTINGS_CHARACTERISTIC), data).toObservable()
    }

    override fun connectDeviceToWriteFactorySettingsByBle(mac: String, data: ByteArray): Observable<Resource<HardwareInfoResponse>> {
        val device: RxBleDevice = bleClient.getBleDevice(mac)
        return Observable.defer {
            //device.bluetoothDevice.createBond()// it is a blocking function
            device.establishConnection(false) // return Observable<RxBleConnection>
        }
        .delay(5, TimeUnit.SECONDS)
        .flatMapSingle { connection ->
            connection.requestMtu(515)
                .flatMap {
                    Single.just(connection)
                }
        }
        .flatMap(
            { connection -> setupNotifications(connection).delay(5, TimeUnit.SECONDS) },
            { connection, deviceCallbacks -> performWrite(connection, deviceCallbacks, data) }
        )
        .flatMap {
            it
        }
        //.take(1) // after the successful write we are no longer interested in the connection so it will be released
        .map {
            Timber.i("Rointe Ble: Result write: ok ->{${it.toHex()}}")
            Resource.success(data = evaluateHardwareInfoResponse(it))
        }
        //.take(1)
        .onErrorReturn {
            Timber.i("Rointe Ble: Result write: failed ->{${it.message.toString()}}")
            Resource.error(data = HardwareInfoResponse.NULL_HARDWARE_INFO_RESPONSE, message = "Error write on device.")
        }
        .doOnError {
            Timber.i("Rointe Ble*","Error getting ble information."+it)
        }
        //.subscribeOn(ioScheduler)
        .observeOn(uiScheduler)
    }

As can be seen, the MTU is negotiated to the maximum and a single packet is sent (json file shown).

When I run my code it connects but shows this error:

com.polidea.rxandroidble2.exceptions.BleCannotSetCharacteristicNotificationException: Cannot find client characteristic config descriptor (code 2) with characteristic UUID 4f4a4554-4520-4341-4c4f-520001000002

Any help on Kotlin?

1

There are 1 best solutions below

0
Dariusz Seweryn On

When I run my code it connects but shows this error:

com.polidea.rxandroidble2.exceptions.BleCannotSetCharacteristicNotificationException: Cannot find client characteristic config descriptor (code 2) with characteristic UUID 4f4a4554-4520-4341-4c4f-520001000002

You can fix this in two ways:

  1. Change your peripheral code to include a Client Characteristic Config Descriptor on the characteristic that you want to use notifications on – this is the preferred way as it would make the peripheral conform to Bluetooth Specification
  2. Use COMPAT mode when setting up notification which does not set CCCD value at all

How to clean UUID's characteristics cache? what happens is the library remember in cache maybe the last UUID registered. How I clean this cache?

It is possible to clear the cache by using BluetoothGatt#refresh and subsequently getting the new set of services which will allow bypassing the library UUID helper — you need to use functions that accept BluetoothGattCharacteristic instead of UUID.

Code that refreshes BluetoothGatt:

RxBleCustomOperation<Void> bluetoothGattRefreshCustomOp = (bluetoothGatt, rxBleGattCallback, scheduler) -> {
    try {
        Method bluetoothGattRefreshFunction = bluetoothGatt.getClass().getMethod("refresh");
        boolean success = (Boolean) bluetoothGattRefreshFunction.invoke(bluetoothGatt);
        if (!success) return Observable.error(new RuntimeException("BluetoothGatt.refresh() returned false"));
        return Observable.<Void>empty().delay(500, TimeUnit.MILLISECONDS);
    } catch (NoSuchMethodException e) {
        return Observable.error(e);
    } catch (IllegalAccessException e) {
        return Observable.error(e);
    } catch (InvocationTargetException e) {
        return Observable.error(e);
    }
};

Code that discovers services bypassing the library caches:

RxBleCustomOperation<List<BluetoothGattService>> discoverServicesCustomOp = (bluetoothGatt, rxBleGattCallback, scheduler) -> {
    boolean success = bluetoothGatt.discoverServices();
    if (!success) return Observable.error(new RuntimeException("BluetoothGatt.discoverServices() returned false"));
    return rxBleGattCallback.getOnServicesDiscovered()
            .take(1) // so this RxBleCustomOperation will complete after the first result from BluetoothGattCallback.onServicesDiscovered()
            .map(RxBleDeviceServices::getBluetoothGattServices);
};