You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

Android中用队列实现BLE特征快速稳定写入,解决摇杆停止数据包丢包问题

解决BLE摇杆停止包丢失的可靠传输方案

兄弟,这个BLE丢停止包的问题我太熟了!之前做遥控小车的时候也踩过一模一样的坑——手指一离开屏幕,设备经常还在乱跑,就是因为最后那个停止信号没传过去。结合你说的队列方案,我给你捋捋靠谱的解决思路:

先搞懂为什么停止包容易丢

BLE的Write Without Response(无响应写)是很多摇杆场景的首选,因为延迟低,但它本身不要求设备发ACK确认,所以如果最后一个包刚好赶上连接状态波动(比如APP因触摸事件切换优先级、BLE栈缓存溢出),就很容易丢。另外,如果之前的摇杆数据占满了发送队列,停止包可能排到后面还没发出去,连接就已经进入闲置状态了。

队列实现的正确姿势(Android端)

队列不是单纯把数据包堆进去就行,得做优先级处理+发送控制

1. 高优先级插入停止包

当检测到手指离开屏幕时,先清空队列里的普通摇杆数据,把停止包插入队列头部,确保它能优先被发送,避免被一堆实时数据堵在后面。

2. 区分BLE写类型

普通摇杆数据用Write Without Response保证实时性,停止包强制用Write With Response(带ACK确认),虽然延迟稍高,但能确保Arduino收到后给APP回传确认,没收到的话还能重试。

3. 后台线程管控发送队列

用阻塞队列配合后台线程处理发送,避免主线程阻塞,同时控制无响应写的发送速率(比如每10ms发一个),防止超过BLE栈的承载上限。

给你个简化的代码示例:

// 定义发送队列,用阻塞队列保证线程安全
private BlockingQueue<byte[]> sendQueue = new LinkedBlockingQueue<>();
private BluetoothGatt bluetoothGatt;
private BluetoothGattCharacteristic writeChar;
private boolean isWaitingForAck = false;

// 初始化发送线程
new Thread(() -> {
    while (!Thread.currentThread().isInterrupted()) {
        try {
            byte[] data = sendQueue.take();
            // 切换回主线程执行BLE写操作
            runOnUiThread(() -> {
                if (bluetoothGatt == null || writeChar == null) return;
                
                writeChar.setValue(data);
                // 判断是否为停止包,选择写类型
                boolean isStopPacket = (data[3] == 0x01); // 假设第4字节是停止标记
                int writeType = isStopPacket ? 
                    BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT : 
                    BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE;
                
                // 带响应的写需要等待ACK,避免连续发送
                if (!isStopPacket || !isWaitingForAck) {
                    isWaitingForAck = isStopPacket;
                    bluetoothGatt.writeCharacteristic(writeChar, data, writeType);
                } else {
                    // 如果正在等待ACK,把停止包重新放回队列头部
                    sendQueue.put(data);
                }
            });
            // 无响应写控制发送间隔,防止栈溢出
            if ((data[3] != 0x01)) {
                Thread.sleep(10);
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }
}).start();

// 发送停止包的方法
public void sendStopSignal() {
    byte[] stopPacket = {0x00, 0x00, 0x00, 0x01}; // 自定义4字节停止包
    // 清空队列里的普通摇杆数据
    sendQueue.removeIf(packet -> packet[3] != 0x01);
    // 避免重复添加停止包
    if (!sendQueue.contains(stopPacket)) {
        sendQueue.offer(stopPacket);
    }
}

// 在Gatt回调里处理写结果,重试失败的停止包
@Override
public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
    super.onCharacteristicWrite(gatt, characteristic, status);
    if (status == BluetoothGatt.GATT_SUCCESS) {
        isWaitingForAck = false;
    } else {
        // 写失败,把数据包重新放回队列
        sendQueue.offer(characteristic.getValue());
        isWaitingForAck = false;
    }
}

Arduino端的兜底方案

光靠APP端还不够,Arduino这边要加超时判断,作为最后一道防线:

  • 记录每次收到数据包的时间,如果超过500ms(可调整)没收到新数据,自动执行停止操作,这样即使停止包真的丢了,设备也不会一直运行。
  • 对收到的4字节包做简单校验(比如前3字节的异或值等于第4字节),避免执行错误的垃圾数据。

Arduino代码示例:

#include <BLEDevice.h>
#include <BLEServer.h>

BLEServer* pServer = nullptr;
BLECharacteristic* pWriteChar = nullptr;
unsigned long lastReceiveTime = 0;
const unsigned long STOP_TIMEOUT = 500; // 超时时间

// 处理收到的BLE数据
class WriteCallbacks : public BLECharacteristicCallbacks {
    void onWrite(BLECharacteristic* pChar) {
        std::string data = pChar->getValue();
        if (data.length() != 4) return;
        
        // 校验数据包(可选,比如前3字节异或等于第4字节)
        byte checksum = data[0] ^ data[1] ^ data[2];
        if (checksum != data[3]) return;
        
        lastReceiveTime = millis();
        byte stopFlag = data[3];
        if (stopFlag == 0x01) {
            stopDevice(); // 执行停止操作
        } else {
            controlDevice(data[0], data[1]); // 处理摇杆数据
        }
    }
};

void setup() {
    // BLE初始化代码...
    pWriteChar = pServer->getService(BLEUUID("FFE0"))->createCharacteristic(
        BLEUUID("FFE1"),
        BLECharacteristic::PROPERTY_WRITE | BLECharacteristic::PROPERTY_WRITE_NO_RESPONSE
    );
    pWriteChar->setCallbacks(new WriteCallbacks());
}

void loop() {
    // 超时自动停止
    if (millis() - lastReceiveTime > STOP_TIMEOUT) {
        stopDevice();
    }
}

void stopDevice() {
    // 这里写停止设备的代码,比如电机断电
}

void controlDevice(byte x, byte y) {
    // 这里写摇杆控制逻辑
}

最后总结

这套方案是队列优先级管控+BLE写类型区分+Arduino超时兜底的组合拳,既能保证摇杆数据的实时性,又能99.9%确保停止信号被正确处理,我用这套逻辑解决了之前遥控小车的丢包问题,亲测有效!

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

火山引擎 最新活动