You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

RxAndroidBle写入外设后等待响应实现及异常解决(Kotlin)

RxAndroidBle写入外设后等待响应及异常解决

问题背景

在Android Kotlin中使用RxAndroidBle与BLE外设交互,已成功实现设备连接和硬件信息读取功能。现在需要向外设写入指定JSON配置数据,并等待外设返回响应(类似HTTP POST的交互逻辑),但尝试实现时出现以下异常:

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

现有读取功能代码:

override fun connectDeviceToGetInfoHardwareByBle(mac: String): Observable<Resource<HardwareInfoResponse>> {
    val device: RxBleDevice = bleClient.getBleDevice(mac)
    return Observable.defer {
        device.bluetoothDevice.createBond()// 阻塞函数
        device.establishConnection(false) // 返回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)
}

尝试的写入逻辑代码:

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()// 阻塞函数
        device.establishConnection(false) // 返回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) // 成功写入后不再关注连接,会释放连接
    .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)
}

需要写入的JSON数据:

{
"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": ""
}

解决方案

1. 解决「Cannot find client characteristic config descriptor」异常

该异常的核心原因是:你尝试设置通知的特征(SET_FACTORY_SETTINGS_CHARACTERISTIC)不支持通知/指示,或者没有对应的Client Characteristic Configuration Descriptor(CCCD,固定UUID:00002902-0000-1000-8000-00805f9b34fb)。

解决步骤:

  • 查阅外设的GATT Profile文档,确认:
    • 外设返回响应的特征是否是另一个独立的特征,而非写入的特征。如果是,需要监听那个响应特征的通知,而非写入特征。
    • 如果写入特征确实需要接收通知,确认外设是否正确配置了该特征的CCCD,并且开启了NOTIFY/INDICATE权限。

2. 正确实现「写入后等待响应」的逻辑

根据外设的响应方式,分两种场景实现:

场景A:外设通过通知/指示返回响应(主流方案)

大多数BLE外设会通过独立的特征发送响应通知,实现代码如下:

// 替换为外设实际的响应特征UUID
private const val RESPONSE_CHARACTERISTIC_UUID = "你的响应特征UUID"

override fun connectDeviceToWriteFactorySettingsByBle(mac: String, data: ByteArray): Observable<Resource<HardwareInfoResponse>> {
    val device = bleClient.getBleDevice(mac)
    val writeCharUuid = UUID.fromString(SET_FACTORY_SETTINGS_CHARACTERISTIC)
    val responseCharUuid = UUID.fromString(RESPONSE_CHARACTERISTIC_UUID)

    return Observable.defer {
        device.establishConnection(false)
    }
    // 请求MTU,简化链式调用
    .flatMapSingle { connection ->
        connection.requestMtu(515).andThen(Single.just(connection))
    }
    .flatMap { connection ->
        // 1. 先设置响应特征的通知
        connection.setupNotification(responseCharUuid)
            .flatMap { notificationObservable ->
                // 2. 通知设置成功后,执行写入操作
                connection.writeCharacteristic(writeCharUuid, data)
                    .toObservable()
                    .ignoreElements() // 忽略写入成功的事件,关注通知的响应
                    .andThen(notificationObservable) // 切换到响应通知流
            }
            .take(1) // 只取第一个响应结果
            .map { responseBytes -> evaluateHardwareInfoResponse(responseBytes) }
            .map { Resource.success(it) }
            .doFinally { connection.disconnect() } // 处理完成后主动断开连接
    }
    .onErrorReturn {
        Timber.i("Rointe Ble: 写入失败 -> ${it.message}")
        Resource.error(HardwareInfoResponse.NULL_HARDWARE_INFO_RESPONSE, "设备写入错误")
    }
    .subscribeOn(ioScheduler) // 必须恢复,避免UI线程阻塞
    .observeOn(uiScheduler)
}

场景B:外设通过Write Response返回响应(仅适用于小数据)

如果外设通过写入操作的响应包返回数据(这种情况数据长度受限,通常小于MTU-3),可以直接处理写入的返回结果:

override fun connectDeviceToWriteFactorySettingsByBle(mac: String, data: ByteArray): Observable<Resource<HardwareInfoResponse>> {
    val device = bleClient.getBleDevice(mac)
    val writeCharUuid = UUID.fromString(SET_FACTORY_SETTINGS_CHARACTERISTIC)

    return Observable.defer {
        device.establishConnection(false)
    }
    .flatMapSingle { connection ->
        connection.requestMtu(515)
            .andThen(connection.writeCharacteristic(writeCharUuid, data))
            .map { responseBytes -> evaluateHardwareInfoResponse(responseBytes) }
            .map { Resource.success(it) }
    }
    .onErrorReturn {
        Timber.i("Rointe Ble: 写入失败 -> ${it.message}")
        Resource.error(HardwareInfoResponse.NULL_HARDWARE_INFO_RESPONSE, "设备写入错误")
    }
    .subscribeOn(ioScheduler)
    .observeOn(uiScheduler)
}

3. 关于长写入的说明

如果你的JSON序列化后的字节数组长度超过MTU-3(这里MTU=515,即最大单次写入512字节),RxAndroidBle会自动处理长写入,将数据分块发送,不需要手动实现分逻辑,直接调用writeCharacteristic即可。

额外优化点

  • 移除不必要的delay(5, TimeUnit.SECONDS):RxAndroidBle的操作都是异步触发的,固定延迟会导致无效等待,甚至错过外设的响应。
  • 确保subscribeOn(ioScheduler)生效:连接、写入等操作必须在IO线程执行,避免阻塞UI线程。
  • 确认外设特征权限:写入特征需要具备WRITE权限,响应特征需要具备NOTIFY/INDICATE权限。

内容的提问来源于stack exchange,提问作者FABiO

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.05 14:00:54