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

Android基于FastBLE向RN4871 BLE模块发数据超量失败求助

问题:Android向RN4871 BLE模块发送数据无法突破字节上限

我在Android应用中无法向BLE模块发送超过160字节的数据,当前使用修改后的FastBLE安卓应用向Microchip RN4871蓝牙模块发送数据,BLE运行在透传UART模式。连接模块后,安卓应用先调用notify函数,再调用write函数以20字节分片发送数据包。

但发送160字节后,安卓应用就不再发送更多数据;增大MTU后最多能发送600字节,但依旧无法突破上限。我们发现修改FastBLE安卓代码后,notify descriptor的值为NULL,由于不清楚原因,无法修复。

调用代码("hex"为待写入字符串)

setCharaProp(2);
((CharacteristicOperationFragment) fragments.get(2)).showData(hex);
setCharaProp(1);
((CharacteristicOperationFragment) fragments.get(2)).showData(hex);
setCharaProp(2);

Notify和Write的实现代码

public void showData(String hex) {
    final BleDevice bleDevice = ((OperationActivity) getActivity()).getBleDevice();
    final BluetoothGattCharacteristic characteristic = ((OperationActivity) getActivity()).getCharacteristic();
    final int charaProp = ((OperationActivity) getActivity()).getCharaProp();
    String child = characteristic.getUuid().toString() + String.valueOf(charaProp);

    for (int i = 0; i < layout_container.getChildCount(); i++) {
        layout_container.getChildAt(i).setVisibility(View.GONE);
    }
    if (childList.contains(child)) {
        layout_container.findViewWithTag(bleDevice.getKey() + characteristic.getUuid().toString() + charaProp).setVisibility(View.VISIBLE);
    } else {
        childList.add(child);
        View view = LayoutInflater.from(getActivity()).inflate(R.layout.layout_characteric_operation, null);
        view.setTag(bleDevice.getKey() + characteristic.getUuid().toString() + charaProp);
        LinearLayout layout_add = (LinearLayout) view.findViewById(R.id.layout_add);
        final TextView txt_title = (TextView) view.findViewById(R.id.txt_title);
        txt_title.setText(String.valueOf(characteristic.getUuid().toString() + getActivity().getString(R.string.data_changed)));
        final TextView txt = (TextView) view.findViewById(R.id.txt);
        txt.setMovementMethod(ScrollingMovementMethod.getInstance());

        switch (charaProp) {
        
            case PROPERTY_WRITE:
                {
                    View view_add = LayoutInflater.from(getActivity()).inflate(R.layout.layout_characteric_operation_et, null);

                    if (TextUtils.isEmpty(hex)) {
                        return;
                    }
                    BleManager.getInstance().write(
                        bleDevice,
                        characteristic.getService().getUuid().toString(),
                        characteristic.getUuid().toString(),
                        HexUtil.hexStringToBytes(hex),
                        new BleWriteCallback() {

                            @Override
                            public void onWriteSuccess(final int current, final int total, final byte[] justWrite) {
                                runOnUiThread(new Runnable() {
                                    @Override
                                    public void run() {
                                        addText(txt, "write success, current: " + current +
                                            " total: " + total +
                                            " justWrite: " + HexUtil.formatHexString(justWrite, true));
                                    }
                                });
                            }

                            @Override
                            public void onWriteFailure(final BleException exception) {
                                runOnUiThread(new Runnable() {
                                    @Override
                                    public void run() {
                                        addText(txt, exception.toString());
                                    }
                                });
                            }
                        });
                    layout_add.addView(view_add);
                }
                break;
                
            case PROPERTY_NOTIFY:
                {
                    View view_add = LayoutInflater.from(getActivity()).inflate(R.layout.layout_characteric_operation_button, null);
                    if (my_notify_ctl == 1) {
                        my_notify_ctl = 0;
                        BleManager.getInstance().notify(
                            bleDevice,
                            characteristic.getService().getUuid().toString(),
                            characteristic.getUuid().toString(),
                            new BleNotifyCallback() {

                                @Override
                                public void onNotifySuccess() {
                                    runOnUiThread(new Runnable() {
                                        @Override
                                        public void run() {
                                            addText(txt, "notify success");
                                        }
                                    });
                                }

                                @Override
                                public void onNotifyFailure(final BleException exception) {
                                    runOnUiThread(new Runnable() {
                                        @Override
                                        public void run() {
                                            addText(txt, exception.toString());
                                        }
                                    });
                                }

                                @Override
                                public void onCharacteristicChanged(byte[] data) {
                                    runOnUiThread(new Runnable() {
                                        @Override
                                        public void run() {
                                            addText(txt, HexUtil.formatHexString(characteristic.getValue(), true));
                                        }
                                    });
                                }
                            });
                    } else {
                        BleManager.getInstance().stopNotify(
                            bleDevice,
                            characteristic.getService().getUuid().toString(),
                            characteristic.getUuid().toString());
                        my_notify_ctl = 1;
                    }
                    layout_add.addView(view_add);
                }
                break;
                
        layout_container.addView(view);
    }
}

问题分析与解决方案

1. Notify Descriptor为NULL的原因

FastBLE调用notify时如果找不到对应的Client Characteristic Configuration Descriptor(UUID: 00002902-0000-1000-8000-00805f9b34fb),会导致descriptor为NULL,通常是因为:

  • 设备端的Notify特征未正确配置该Descriptor
  • 连接BLE设备后,未完整完成服务/特征/Descriptor发现就调用了notify

2. 发送字节上限问题的核心原因

  • FastBLE分片逻辑缺陷:默认异步分片可能存在队列阻塞,未等待前一片发送完成就触发下一片发送
  • RN4871透传模式限制:RN4871在透传UART模式下对连续写入速率有要求,无流量控制会导致设备拒绝接收或丢包
  • Notify与Write资源竞争:连续切换charaProp调用两个操作,会抢占BLE链路资源

3. 具体修复步骤

(1)确保Notify Descriptor正常获取

连接设备时必须等待服务发现完成后再操作:

BleManager.getInstance().connect(bleDevice, new BleConnectCallback() {
    @Override
    public void onConnectSuccess(BleDevice bleDevice, BluetoothGatt gatt, int status) {
        // 触发服务发现
        gatt.discoverServices();
    }

    @Override
    public void onServicesDiscovered(BleDevice bleDevice, BluetoothGatt gatt) {
        // 服务发现完成后,再执行notify和write逻辑
    }
});

(2)自定义分片发送逻辑

在onWriteSuccess回调中触发下一片发送,避免队列阻塞:

public class BleBatchWriter {
    private BleDevice bleDevice;
    private String serviceUuid;
    private String charaUuid;
    private byte[] totalData;
    private int chunkSize;
    private int currentIndex = 0;
    private BleWriteCallback callback;

    public BleBatchWriter(BleDevice bleDevice, String serviceUuid, String charaUuid, byte[] totalData, int mtu, BleWriteCallback callback) {
        this.bleDevice = bleDevice;
        this.serviceUuid = serviceUuid;
        this.charaUuid = charaUuid;
        this.totalData = totalData;
        this.chunkSize = mtu - 3; // 减去BLE层3字节开销
        this.callback = callback;
    }

    public void start() {
        sendNextChunk();
    }

    private void sendNextChunk() {
        if (currentIndex >= totalData.length) {
            callback.onWriteSuccess(totalData.length, totalData.length, new byte[0]);
            return;
        }
        int end = Math.min(currentIndex + chunkSize, totalData.length);
        byte[] chunk = Arrays.copyOfRange(totalData, currentIndex, end);
        
        BleManager.getInstance().write(bleDevice, serviceUuid, charaUuid, chunk, new BleWriteCallback() {
            @Override
            public void onWriteSuccess(int current, int total, byte[] justWrite) {
                currentIndex += justWrite.length;
                callback.onWriteSuccess(currentIndex, totalData.length, justWrite);
                sendNextChunk();
            }

            @Override
            public void onWriteFailure(BleException exception) {
                callback.onWriteFailure(exception);
            }
        });
    }
}

// 使用示例
new BleBatchWriter(bleDevice, serviceUuid, charaUuid, HexUtil.hexStringToBytes(hex), mtuSize, new BleWriteCallback() {
    @Override
    public void onWriteSuccess(int current, int total, byte[] justWrite) {
        // 处理成功回调
    }

    @Override
    public void onWriteFailure(BleException exception) {
        // 处理失败回调
    }
}).start();

(3)RN4871端配置优化

开启流量控制避免丢包:

  • 发送$SF,1开启软件流量控制(Android端需配合发送XON/XOFF)
  • 或发送$SF,2开启硬件流量控制(需硬件支持)

(4)避免Notify与Write资源竞争

先确保Notify启动成功后再执行Write:

BleManager.getInstance().notify(bleDevice, notifyServiceUuid, notifyCharaUuid, new BleNotifyCallback() {
    @Override
    public void onNotifySuccess() {
        // Notify启动成功后执行批量Write
        new BleBatchWriter(bleDevice, writeServiceUuid, writeCharaUuid, data, mtuSize, writeCallback).start();
    }

    @Override
    public void onNotifyFailure(BleException exception) {
        // 处理Notify失败
    }

    @Override
    public void onCharacteristicChanged(byte[] data) {
        // 处理设备回传数据
    }
});

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.20 22:33:34