hejianhao
2026-05-08 f5aace93e5e8b0eb80576c1185454001df9e9cc1
refactor(蓝牙控制): 重构蓝牙连接与重试逻辑,移除从地址切换机制

- 移除 slaveMacList 支持,仅使用主蓝牙地址进行连接
- 改进设备型号解析,提取尖括号内硬件型号
- 引入状态失败后的自动重连机制,避免无限重试
- 优化连接状态管理,增加解锁成功后的状态抑制
- 清理冗余代码,提高蓝牙控制模块的可维护性
1个文件已修改
153 ■■■■■ 已修改文件
src/robotaxi/pages/begin-route/hooks/use-bluetooth-car-control.ts 153 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/hooks/use-bluetooth-car-control.ts
@@ -12,12 +12,12 @@
type BluetoothTokenPayload = {
  token?: string; // 鉴权凭证
  macAddress?: string; // 主地址(字符串)
  slaveMacList?: string[]; // 从地址列表(字符串数组)
};
type ConnectStatus = 0 | 1 | 2 | 3;
const RETRY_UNLOCK_MESSAGE = '请点击解锁按钮解锁车门'; // 鉴权失败/解锁失败统一提示文案
const UNLOCK_SUCCESS_MESSAGE = '车门已解锁,请上车'; // 解锁成功提示文案
const RETRY_DELAY_MS = 1500; // 状态失败后的自动重连延迟毫秒数
/**
 * 归一化 MAC 地址,统一去空格并转大写。
@@ -27,35 +27,20 @@
}
/**
 * 从 token 接口结果构建蓝牙地址候选列表:主地址优先,其次从地址列表,自动去重。
 */
function buildMacCandidates(macAddress: unknown, slaveMacList: unknown): string[] {
  const candidates = [normalizeMac(macAddress)];
  if (Array.isArray(slaveMacList)) {
    slaveMacList.forEach((item) => {
      candidates.push(normalizeMac(item));
    });
  }
  return Array.from(new Set(candidates.filter(Boolean)));
}
/**
 * 判断错误是否属于可切换 MAC 重试的连接链路错误。
 */
function isRetryableConnectError(error: any): boolean {
  const type = String(error?.type || '').toUpperCase();
  if (type === 'CONNECT_ERR' || type === 'ADAPTER_ERR') return true;
  const msg = String(error?.msg || error?.errMsg || '');
  return /(connect|连接|adapter|蓝牙适配器|timeout|超时|discovery)/i.test(msg);
}
/**
 * 解析设备型号,优先使用系统型号。
 */
function resolveDeviceModel(): string {
  try {
    const info = Taro.getSystemInfoSync();
    return info?.model || 'MiniProgram Device';
    const rawModel = String(info?.model || '').trim(); // 小程序系统返回的原始机型字段
    const bracketMatchedModel = rawModel.match(/<\s*([^>]+)\s*>/); // 提取尖括号内硬件型号,例如 iPhone18,4
    if (bracketMatchedModel?.[1]) {
      return bracketMatchedModel[1].replace(/\s+/g, '');
    }
    if (rawModel) {
      return rawModel.replace(/\s+/g, '');
    }
    return 'MiniProgram Device';
  } catch (error) {
    return 'MiniProgram Device';
  }
@@ -68,10 +53,12 @@
  const managerRef = useRef<any>(null); // 蓝牙管理器实例引用
  const lastStartKeyRef = useRef(''); // 最近一次启动鉴权的参数签名
  const startAuthTaskRef = useRef<Promise<void> | null>(null); // 当前进行中的鉴权任务,防止并发重复请求
  const candidateMacsRef = useRef<string[]>([]); // 蓝牙地址候选列表(主 + 从)
  const currentMacIndexRef = useRef(0); // 当前尝试的 MAC 索引
  const certificateRef = useRef(''); // 当前会话证书缓存
  const macAddressRef = useRef(''); // 当前会话主蓝牙地址缓存
  const deviceModelRef = useRef(resolveDeviceModel()); // 当前设备型号缓存
  const retryTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null); // 状态失败后的自动重连定时器
  const suppressAutoRetryRef = useRef(false); // 主动关闭/解锁成功后的自动重连抑制标记
  const unlockFinishedRef = useRef(false); // 当前轮次是否已出现解锁成功
  const [connectStatus, setConnectStatus] = useState<ConnectStatus>(0); // 蓝牙连接状态:0断开/1连接中/2已连接/3已鉴权
  const [authSuccess, setAuthSuccess] = useState(false); // RtAuth 鉴权是否成功
  const [isAuthorizing, setIsAuthorizing] = useState(false); // 鉴权流程进行中
@@ -86,26 +73,51 @@
  }, []); // 统一展示蓝牙解锁结果提示
  /**
   * 切换到下一个候选 MAC 重试连接。
   * 清理自动重连定时器。
   */
  const tryFallbackMacConnection = useCallback(() => {
    if (currentMacIndexRef.current >= candidateMacsRef.current.length - 1) {
      return false;
  const clearRetryTimer = useCallback(() => {
    if (retryTimerRef.current) {
      clearTimeout(retryTimerRef.current);
      retryTimerRef.current = null;
    }
    const nextIndex = currentMacIndexRef.current + 1;
    const nextMac = candidateMacsRef.current[nextIndex];
    if (!nextMac || !managerRef.current) {
      return false;
  }, []);
  /**
   * 主动断开蓝牙连接并重置状态。
   */
  const closeConnection = useCallback(() => {
    console.log('[蓝牙] 主动断开连接');
    suppressAutoRetryRef.current = true;
    clearRetryTimer();
    if (managerRef.current) {
      managerRef.current.closeConnection();
    }
    currentMacIndexRef.current = nextIndex;
    console.log('[蓝牙] 当前地址连接失败,切换从地址重试:', nextMac);
    managerRef.current.updateCarConfig({
      macAddress: nextMac,
      certificate: certificateRef.current,
      deviceModel: deviceModelRef.current
    });
    managerRef.current.retryConnection();
    return true;
    authSuccessRef.current = false;
    isAuthorizingRef.current = false;
    startAuthTaskRef.current = null;
    setConnectStatus(0);
    setAuthSuccess(false);
    setIsAuthorizing(false);
  }, [clearRetryTimer]);
  /**
   * 调度一次状态失败后的自动重连。
   */
  const scheduleRetryConnection = useCallback(() => {
    if (retryTimerRef.current || suppressAutoRetryRef.current || unlockFinishedRef.current) {
      return;
    }
    retryTimerRef.current = setTimeout(() => {
      retryTimerRef.current = null;
      if (suppressAutoRetryRef.current || unlockFinishedRef.current || !managerRef.current) {
        return;
      }
      isAuthorizingRef.current = true;
      setIsAuthorizing(true);
      setAuthError('');
      console.log('[蓝牙] status 非 3,触发延迟重连');
      managerRef.current.retryConnection();
    }, RETRY_DELAY_MS);
  }, []);
  /**
@@ -120,19 +132,22 @@
        console.log('[蓝牙] 连接状态变更:', status);
        setConnectStatus(status);
        if (status === 3) {
          clearRetryTimer();
          authSuccessRef.current = true;
          isAuthorizingRef.current = false;
          setAuthSuccess(true);
          setAuthError('');
          setIsAuthorizing(false);
          return;
        }
        scheduleRetryConnection();
      },
      onError: (error: any) => {
        console.log('[蓝牙] 连接错误:', error);
        if (isRetryableConnectError(error) && tryFallbackMacConnection()) {
        isAuthorizingRef.current = false;
        if (unlockFinishedRef.current) {
          return;
        }
        isAuthorizingRef.current = false;
        setAuthError(error?.msg || '蓝牙连接失败');
        setIsAuthorizing(false);
      },
@@ -156,8 +171,10 @@
        console.log('[蓝牙] 解锁通知:', result);
        if (result?.type === 'pollingUnlock') {
          if (result?.success) {
            unlockFinishedRef.current = true;
            setAuthError('');
            showUnlockToast(UNLOCK_SUCCESS_MESSAGE);
            closeConnection();
          } else {
            setAuthError(RETRY_UNLOCK_MESSAGE);
            showUnlockToast(RETRY_UNLOCK_MESSAGE);
@@ -165,29 +182,16 @@
          return;
        }
        if (result?.type === 'error') {
          if (unlockFinishedRef.current) {
            return;
          }
          setAuthError(RETRY_UNLOCK_MESSAGE);
          showUnlockToast(RETRY_UNLOCK_MESSAGE);
        }
      }
    });
    return managerRef.current;
  }, [showUnlockToast, tryFallbackMacConnection]);
  /**
   * 主动断开蓝牙连接并重置状态。
   */
  const closeConnection = useCallback(() => {
    console.log('[蓝牙] 主动断开连接');
    if (managerRef.current) {
      managerRef.current.closeConnection();
    }
    authSuccessRef.current = false;
    isAuthorizingRef.current = false;
    startAuthTaskRef.current = null;
    setConnectStatus(0);
    setAuthSuccess(false);
    setIsAuthorizing(false);
  }, []);
  }, [showUnlockToast, closeConnection, scheduleRetryConnection, clearRetryTimer]);
  /**
   * 触发蓝牙连接与 RtAuth 鉴权。
@@ -206,6 +210,9 @@
    const startKey = `${orderId}`;
    if (isAuthorizingRef.current || (authSuccessRef.current && lastStartKeyRef.current === startKey)) return;
    lastStartKeyRef.current = startKey;
    suppressAutoRetryRef.current = false;
    unlockFinishedRef.current = false;
    clearRetryTimer();
    isAuthorizingRef.current = true;
    setIsAuthorizing(true);
    setAuthError('');
@@ -214,27 +221,26 @@
        const tokenRes = await getBluetoothUnlockToken(orderId);
        const tokenPayload: BluetoothTokenPayload = typeof tokenRes === 'object' && tokenRes !== null ? tokenRes as BluetoothTokenPayload : {};
        const certificate = typeof tokenPayload.token === 'string' ? tokenPayload.token : String(tokenPayload.token || '');
        const candidateMacs = buildMacCandidates(tokenPayload.macAddress, tokenPayload.slaveMacList);
        console.log('[蓝牙] 获取凭证与地址成功:', { tokenLength: certificate?.length || 0, macCount: candidateMacs.length });
        const mainMacAddress = normalizeMac(tokenPayload.macAddress);
        console.log('[蓝牙] 获取凭证与主地址成功:', { tokenLength: certificate?.length || 0, hasMainMac: Boolean(mainMacAddress) });
        if (!certificate) {
          isAuthorizingRef.current = false;
          setAuthError('蓝牙鉴权凭证为空');
          setIsAuthorizing(false);
          return;
        }
        if (candidateMacs.length === 0) {
        if (!mainMacAddress) {
          isAuthorizingRef.current = false;
          setAuthError('蓝牙地址为空');
          setIsAuthorizing(false);
          return;
        }
        certificateRef.current = certificate;
        candidateMacsRef.current = candidateMacs;
        currentMacIndexRef.current = 0;
        macAddressRef.current = mainMacAddress;
        const manager = ensureManager();
        console.log('[蓝牙] 更新车辆配置(主地址):', { macAddress: candidateMacs[0], deviceModel: deviceModelRef.current });
        console.log('[蓝牙] 更新车辆配置(主地址):', { macAddress: mainMacAddress, deviceModel: deviceModelRef.current });
        manager.updateCarConfig({
          macAddress: candidateMacs[0],
          macAddress: mainMacAddress,
          certificate,
          deviceModel: deviceModelRef.current
        });
@@ -274,23 +280,24 @@
      console.log('[蓝牙] 当前状态非4/5,断开连接:', tripStatus);
      closeConnection();
      lastStartKeyRef.current = '';
      candidateMacsRef.current = [];
      currentMacIndexRef.current = 0;
      certificateRef.current = '';
      macAddressRef.current = '';
      startAuthTaskRef.current = null;
      authSuccessRef.current = false;
      isAuthorizingRef.current = false;
      unlockFinishedRef.current = false;
      setAuthError('');
    }
  }, [tripStatus, closeConnection]);
  useEffect(() => {
    return () => {
      clearRetryTimer();
      if (managerRef.current) {
        managerRef.current.closeConnection();
      }
    };
  }, []);
  }, [clearRetryTimer]);
  return {
    connectStatus,