Web Bluetooth API问题:重连蓝牙设备后无法写入数据
你的代码核心问题集中在连接状态的同步管理和断开事件的处理不完整,导致重连后状态不一致,进而引发写入操作无响应。以下是具体问题点和修正步骤:
1. 断开事件未同步更新React状态
原disconnected函数仅打印日志,未更新isConnected和scoreboardDeviceServer状态,导致UI显示的连接状态与实际设备状态脱节。重连时可能使用已经失效的服务器引用,引发写入操作挂起。
修正:
更新disconnected函数同步状态,并用useEffect管理事件监听器避免重复绑定:
const disconnected = () => { console.log('Scoreboard is disconnected.'); setIsConnected(false); setScoreboardDeviceServer(null); }; // 管理蓝牙设备断开事件监听器 useEffect(() => { if (scoreboardDevice) { scoreboardDevice.addEventListener('gattserverdisconnected', disconnected); // 组件卸载时移除监听器 return () => { scoreboardDevice.removeEventListener('gattserverdisconnected', disconnected); }; } }, [scoreboardDevice]);
2. 重连时状态更新时机错误
原onConnect函数在connectScoreboard执行完成后直接设置isConnected=true,但setScoreboardDeviceServer是异步状态更新,此时scoreboardDeviceServer可能尚未就绪,导致后续写入操作使用无效引用。
修正:
将setIsConnected(true)移至connectScoreboard内部,确保获取到有效GATT服务器实例后再标记连接成功,并移除onConnect末尾的冗余状态设置:
const connectScoreboard = async () => { const options = { filters: [{ namePrefix: 'CricNet' }], optionalServices: ['5a0d6a15-b664-4304-8530-3a0ec53e5bc1'] }; let device = scoreboardDevice; if (!device) { console.log('Pairing with bluetooth scoreboard ...'); device = await navigator.bluetooth.requestDevice(options); setScoreboardDevice(device); } console.log('Connecting to bluetooth scoreboard ...'); const deviceServer = await device.gatt.connect(); setScoreboardDeviceServer(deviceServer); // 确认连接成功后更新状态 setIsConnected(true); };
3. 写入操作依赖滞后的状态判断
原write函数依赖isConnected状态判断,但React状态更新是异步的,可能出现设备已断开但isConnected仍为true的情况,导致写入操作使用无效的服务器引用。
修正:
直接检查设备的实际连接状态和服务器实例有效性,并在写入失败时强制同步断开状态:
const write = async (total) => { const buffer = new ArrayBuffer(6); const view = new Int16Array(buffer); view[0] = -1896; view[1] = 0; view[2] = total; // 直接检查设备连接状态和服务器实例 if (scoreboardDevice?.gatt?.connected && scoreboardDeviceServer) { try { console.log('Getting service ...'); const service = await scoreboardDeviceServer.getPrimaryService('5a0d6a15-b664-4304-8530-3a0ec53e5bc1'); console.log('Getting characteristic ...'); const characteristic = await service.getCharacteristic('df531f62-fc0b-40ce-81b2-32a6262ea440'); await characteristic.writeValueWithoutResponse(buffer); console.log('Write operation initiated'); } catch (e) { // 打印详细错误信息便于排查 console.error('Write failed:', e); // 写入失败时强制同步断开状态 setIsConnected(false); setScoreboardDeviceServer(null); } } };
4. 断开函数逻辑优化
原onDisconnect函数中,scoreboardDevice.gatt.connected的检查可能存在延迟,直接调用disconnect()后立即更新状态即可:
const onDisconnect = () => { if (isConnected && scoreboardDevice) { console.log('Disconnecting ...'); scoreboardDevice.gatt.disconnect(); setScoreboardDeviceServer(null); setIsConnected(false); } };
完整修正后的代码
import { useEffect, useState } from 'react' function useBluetoothScoreboard(options) { const [isBluetoothAvailable, setIsBluetoothAvailable] = useState(false); const [connectionInProgress, setConnectionInProgress] = useState(false); const [isConnected, setIsConnected] = useState(false); const [scoreboardDevice, setScoreboardDevice] = useState(null); const [scoreboardDeviceServer, setScoreboardDeviceServer] = useState(null); useEffect(() => { navigator.bluetooth.getAvailability() .then((available) => setIsBluetoothAvailable(available)) .catch(() => setIsBluetoothAvailable(false)); }, []); const disconnected = () => { console.log('Scoreboard is disconnected.'); setIsConnected(false); setScoreboardDeviceServer(null); }; // 管理蓝牙设备断开事件监听器 useEffect(() => { if (scoreboardDevice) { scoreboardDevice.addEventListener('gattserverdisconnected', disconnected); return () => { scoreboardDevice.removeEventListener('gattserverdisconnected', disconnected); }; } }, [scoreboardDevice]); const onConnect = async () => { try { if (!isConnected && !connectionInProgress) { const connectScoreboard = async () => { const options = { filters: [{ namePrefix: 'CricNet' }], optionalServices: ['5a0d6a15-b664-4304-8530-3a0ec53e5bc1'] }; let device = scoreboardDevice; if (!device) { console.log('Pairing with bluetooth scoreboard ...'); device = await navigator.bluetooth.requestDevice(options); setScoreboardDevice(device); } console.log('Connecting to bluetooth scoreboard ...'); const deviceServer = await device.gatt.connect(); setScoreboardDeviceServer(deviceServer); setIsConnected(true); }; setConnectionInProgress(true); await connectScoreboard(); setConnectionInProgress(false); } } catch (e) { console.error('Connection failed:', e); setConnectionInProgress(false); setIsConnected(false); } }; const onDisconnect = () => { if (isConnected && scoreboardDevice) { console.log('Disconnecting ...'); scoreboardDevice.gatt.disconnect(); setScoreboardDeviceServer(null); setIsConnected(false); } }; const write = async (total) => { const buffer = new ArrayBuffer(6); const view = new Int16Array(buffer); view[0] = -1896; view[1] = 0; view[2] = total; if (scoreboardDevice?.gatt?.connected && scoreboardDeviceServer) { try { console.log('Getting service ...'); const service = await scoreboardDeviceServer.getPrimaryService('5a0d6a15-b664-4304-8530-3a0ec53e5bc1'); console.log('Getting characteristic ...'); const characteristic = await service.getCharacteristic('df531f62-fc0b-40ce-81b2-32a6262ea440'); await characteristic.writeValueWithoutResponse(buffer); console.log('Write operation initiated'); } catch (e) { console.error('Write failed:', e); setIsConnected(false); setScoreboardDeviceServer(null); } } }; return { isBluetoothAvailable, connectionInProgress, isConnected, onConnect, write, onDisconnect }; } export default useBluetoothScoreboard;
额外排查建议
如果修正后问题仍存在,需要确认树莓派端的蓝牙服务器是否处理了断开后的连接清理:
- 检查树莓派蓝牙服务是否在客户端断开后释放连接资源
- 确认服务器端是否允许同一设备多次连接
- 排查蓝牙模块的连接数限制或会话缓存问题
内容的提问来源于stack exchange,提问作者Targett-Adams

