config/BuildDonePlugin.js
New file @@ -0,0 +1,45 @@ const fs = require('fs-extra'); const path = require('path'); // 需要在构建完成后拷贝到产物目录的文件列表 // src: 相对于项目根目录的源文件路径 // dest: 相对于产物目录(outputPath)的目标文件路径 const filesToCopy = [ // { // src: 'src/otherPage/pages/carControl/bleMethod.wasm.br', // dest: 'otherPage/pages/carControl/bleMethod.wasm.br', // }, { src: 'src/robotaxi/utils/car-control/bleMethod.wasm.br', dest: 'robotaxi/utils/car-control/bleMethod.wasm.br', }, ]; class BuildDonePlugin { apply(compiler) { compiler.hooks.done.tap('BuildDonePlugin', (stats) => { if (process.env.TARO_ENV !== 'weapp') return; const outputPath = stats.compilation.outputOptions.path; filesToCopy.forEach(({ src, dest }) => { const srcFile = path.resolve(src); const destFile = path.resolve(outputPath, dest); const destDir = path.dirname(destFile); if (!fs.existsSync(destDir)) { fs.mkdirSync(destDir, { recursive: true }); } try { fs.copyFileSync(srcFile, destFile); console.log(`✅ copy: ${src} -> ${dest}`); } catch (error) { console.error(`❌ copy: ${src} -> ${dest}: ${error.message}`); } }); }); } } module.exports = BuildDonePlugin; config/dev.js
@@ -116,6 +116,9 @@ }, ], }, buildDone: { plugin: require('./BuildDonePlugin'), }, }, }) }, config/index.js
@@ -48,16 +48,6 @@ ] } class BuildDonePlugin { apply(compiler) { compiler.hooks.done.tap('Build Done Plugin', () => { // if (process.env.TARO_ENV === 'alipay') { // fs.copyFileSync('./mini.project.json', './dist/alipay/mini.project.json') // } }); } } const config = { projectName: "xp-robotaxi-miniprogram", date: "2020-2-11", @@ -138,9 +128,9 @@ webpackChain(chain) { chain.merge({ plugin: { install: { plugin: BuildDonePlugin } buildDone: { plugin: require('./BuildDonePlugin'), }, } }); } src/robotaxi/api/begin-route.ts
@@ -194,3 +194,12 @@ payload }, true); } // 订单查询队列状态 export const queryOrderQueueStatus = async (payload) => { return request({ url: _ROBOTAXI_HOST + `/itinerary/base/v1/mini-program/order/queue-status`, method: 'GET', payload }, true); } src/robotaxi/pages/begin-route/components/begin-route-view.tsx
@@ -34,6 +34,7 @@ originBoxHidden: boolean; originOpacity: number; toOrigin: () => void; handleWalkNavigation: () => void; originIcon: string; selectRouteIcon: string; packUpIcon: string; @@ -95,6 +96,7 @@ originBoxHidden, originOpacity, toOrigin, handleWalkNavigation, originIcon, selectRouteIcon, packUpIcon, @@ -162,6 +164,9 @@ <View className='container-inner'> {shouldShowOrigin && ( <View className='origin-box' style={{ opacity: originBoxHidden ? 0 : originOpacity, transition: 'opacity 0.2s ease-out', pointerEvents: originBoxHidden ? 'none' : 'auto' }}> {[4, 5].includes(tripStatus) && ( <Image onClick={handleWalkNavigation} style={{ marginBottom: '16px' }} className='origin-icon' src={walkIcon} /> )} <Image onClick={toOrigin} className='origin-icon' src={originIcon} /> { canToggleRouteSelect && src/robotaxi/pages/begin-route/components/calling-car-modules/trip-action-bar.tsx
@@ -1,4 +1,4 @@ import { View, Text, Image } from "@tarojs/components"; import { View, Text, Image, Button } from "@tarojs/components"; import changeDestinationIcon from "../../../../assets/change-destination.png"; import shareIcon from "../../../../assets/share-trip.png"; import cancelIcon from "../../../../assets/cancel-trip.png"; @@ -48,15 +48,28 @@ return ( <View className="trip-action-bar"> {actionList.map(item => ( <View key={item.key} className="trip-action-bar-item" hoverClass="trip-action-bar-item-hover" onClick={() => item.onClick?.()} > <Image className="trip-action-bar-item-icon" src={item.icon} /> <Text className="trip-action-bar-item-text">{item.label}</Text> </View> item.key === 'share' ? ( <Button key={item.key} className="trip-action-bar-item trip-action-bar-share-btn" hoverClass="trip-action-bar-item-hover" openType="share" onClick={() => item.onClick?.()} > <Image className="trip-action-bar-item-icon" src={item.icon} /> <Text className="trip-action-bar-item-text">{item.label}</Text> </Button> ) : ( <View key={item.key} className="trip-action-bar-item" hoverClass="trip-action-bar-item-hover" onClick={() => item.onClick?.()} > <Image className="trip-action-bar-item-icon" src={item.icon} /> <Text className="trip-action-bar-item-text">{item.label}</Text> </View> ) ))} </View> ); src/robotaxi/pages/begin-route/components/calling-car-modules/trip-status-header.tsx
@@ -1,8 +1,17 @@ import Taro from "@tarojs/taro"; import { View, Image } from "@tarojs/components"; import { useEffect, useState } from "react"; import { formatCountdown } from '../../../../../utils/Robotaxi-public-method'; import { queryOrderQueueStatus } from '../../../../api/begin-route'; type QueueStatus = { currentPosition: number; estimatedWaitTime: number; status: number; totalCount: number; } | null; type Props = { orderId?: string; tripStatus: number; waitMinutes: number | null; durationDisplay: { value: string | number; unit: string }; @@ -16,6 +25,7 @@ }; export default function TripStatusHeader({ orderId, tripStatus, waitMinutes, durationDisplay, @@ -27,6 +37,56 @@ limitMinutes, handleOpenCancelPopup }: Props) { const [queueStatus, setQueueStatus] = useState<QueueStatus>(null); useEffect(() => { if (tripStatus !== 3 || !orderId) { setQueueStatus(null); return; } let timer: ReturnType<typeof setInterval> | null = null; let unmounted = false; // 排队位次是动态变化的,状态为 3 时定时刷新 const fetchQueueStatus = async () => { try { const res = await queryOrderQueueStatus({ orderId }); if (unmounted) { return; } const queueData = res?.data ?? res; if ( Number(queueData?.currentPosition) > 0 && Number(queueData?.totalCount) > 0 ) { setQueueStatus({ currentPosition: Number(queueData.currentPosition), estimatedWaitTime: Number(queueData.estimatedWaitTime) || 0, status: Number(queueData.status) || 0, totalCount: Number(queueData.totalCount) }); return; } setQueueStatus(null); } catch (_) { if (!unmounted) { setQueueStatus(null); } } }; fetchQueueStatus(); timer = setInterval(fetchQueueStatus, 10000); return () => { unmounted = true; if (timer) { clearInterval(timer); } }; }, [tripStatus, orderId]); const titleMap = { 3: <>预计<View className="time">{waitMinutes}</View>分钟内有车应答</>, 4: <>车辆还有<View className="time">{durationDisplay.value}</View>{durationDisplay.unit}到达</>, @@ -55,7 +115,13 @@ <View className='calling-car-content-title'>{titleMap[tripStatus]}</View> )} {descMap[tripStatus] && ( <View style={{ paddingBottom: tripStatus === 3 ? '20px' : '0px' }} className={descMap[tripStatus]?.className}>{descMap[tripStatus]?.text}</View> // <View style={{ paddingBottom: tripStatus === 3 ? '20px' : '0px' }} className={descMap[tripStatus]?.className}>{descMap[tripStatus]?.text}</View> <View className={descMap[tripStatus]?.className}>{descMap[tripStatus]?.text}</View> )} {tripStatus === 3 && queueStatus && ( <View className='calling-car-content-desc'> 第{queueStatus.currentPosition}位/共{queueStatus.totalCount}位 </View> )} </View> { @@ -66,4 +132,4 @@ } </View> ); } } src/robotaxi/pages/begin-route/components/calling-car.tsx
@@ -114,6 +114,7 @@ <Image className="calling-car-bg" src='https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/site-top-bg.png' /> <TripStatusHeader orderId={orderId} tripStatus={tripStatus} waitMinutes={waitMinutes} durationDisplay={durationDisplay} src/robotaxi/pages/begin-route/components/css-folder/calling-car.less
@@ -179,6 +179,19 @@ color: rgba(0, 0, 0, 0.9); } } &-share-btn { padding: 0; margin: 0; min-width: auto; line-height: normal; background: transparent; border: none; &::after { border: none; } } } &-arrive { src/robotaxi/pages/begin-route/index.tsx
@@ -1,4 +1,4 @@ import Taro, { useDidShow } from "@tarojs/taro" import Taro, { useDidShow, useShareAppMessage } from "@tarojs/taro" import { useEffect, useState, useRef, useMemo } from 'react'; import { View, Image } from '@tarojs/components'; import './index.less'; @@ -114,8 +114,8 @@ 1: { showPickup: true, showDropoff: true, showCar: false, showRoute: true, showWalking: true, viewport: 'route_walking', translateCar: false }, 2: { showPickup: true, showDropoff: false, showCar: false, showRoute: false, showWalking: false, viewport: 'pickup', translateCar: false }, 3: { showPickup: true, showDropoff: false, showCar: false, showRoute: false, showWalking: false, viewport: 'pickup', translateCar: false }, 4: { showPickup: true, showDropoff: false, showCar: true, showRoute: true, showWalking: false, viewport: 'route', translateCar: true }, 5: { showPickup: true, showDropoff: false, showCar: true, showRoute: false, showWalking: false, viewport: 'car_user', translateCar: true }, 4: { showPickup: true, showDropoff: false, showCar: true, showRoute: true, showWalking: true, viewport: 'route', translateCar: true }, 5: { showPickup: true, showDropoff: false, showCar: true, showRoute: false, showWalking: true, viewport: 'car_user', translateCar: true }, 6: { showPickup: false, showDropoff: true, showCar: true, showRoute: true, showWalking: false, viewport: 'route', translateCar: true }, 7: { showPickup: false, showDropoff: true, showCar: true, showRoute: false, showWalking: false, viewport: 'dropoff', translateCar: false }, 8: { showPickup: true, showDropoff: true, showCar: false, showRoute: true, showWalking: false, viewport: 'order_detail', translateCar: false }, @@ -386,6 +386,21 @@ getCurrentLongitude: () => longitude, getCurrentLatitude: () => latitude }); // 页面恢复与初始化编排 const { openService, toCoupon, toOrder, toBack, handleCalloutTap, toSelectSite } = usePageActions({ tripStatus, startSite, getCheckedEnter: () => isCheckedEnter.current, setCheckedEnter: (checked) => { isCheckedEnter.current = checked; } }); // 页面动作编排 const router = Taro.useRouter(); const tripRuntime = useTripRuntime({ waitingTimerStartKey: WAITING_TIMER_START_KEY, @@ -569,6 +584,13 @@ latitude, longitude ]); const shareOrderId = orderId || Taro.getStorageSync('orderId') || ''; const onShareAppMessage = () => ({ title: '快来查看我的 Robotaxi 行程', path: `/robotaxi/pages/share-trip/index?orderId=${shareOrderId}` }); useShareAppMessage(onShareAppMessage); useDidShow(() => { handleDidShow(router.params?.skipPageFlag); @@ -617,7 +639,6 @@ }; const handleShareTrip = () => { isCheckedEnter.current = true; guardPrivacyThenNavigate('/robotaxi/pages/share-trip/index'); }; const { renderMapOverlays, renderTripCard } = useViewDelegates({ isAndroid, @@ -691,6 +712,19 @@ isCheckedEnter.current = true; toOrder(); }; const handleWalkNavigation = () => { if (![4, 5].includes(tripStatus) || !startSite?.latitude || !startSite?.longitude) { return; } Taro.openLocation({ latitude: Number(startSite.latitude), longitude: Number(startSite.longitude), name: startSite.name || '上车点', address: startSite.address || startSite.siteAddress || '', scale: 18 }); }; return ( <BeginRouteView @@ -717,6 +751,7 @@ originBoxHidden={originBoxHidden} originOpacity={originOpacity} toOrigin={toOrigin} handleWalkNavigation={handleWalkNavigation} selectRouteIcon={selectRouteIcon} packUpIcon={packUpIcon} onSelectRouteClick={() => setIsRouteSelectExpanded(!isRouteSelectExpanded)} src/robotaxi/pages/site-search/components/site-list-item.tsx
@@ -1,7 +1,7 @@ import Taro from '@tarojs/taro'; import { View, Image } from '@tarojs/components'; import { useState, useEffect } from 'react'; import { collectPoint } from '../../../api/site-search'; import { collectPoint, getCollectPoints } from '../../../api/site-search'; type Props = { // 站点数据 @@ -33,16 +33,43 @@ onSelect, renderHighlightedText }: Props) { const MAX_COLLECT_COUNT = 50; const [isCollected, setIsCollected] = useState(!!item.isCollected); useEffect(() => { setIsCollected(item.isCollect === 1); }, [item.isCollect]); const checkCollectLimit = async () => { // 只取总数即可,避免为校验拉完整列表 const res = await getCollectPoints({ pageNum: 1, pageSize: 1 }); if (Number(res?.total) >= MAX_COLLECT_COUNT) { Taro.showToast({ title: `最多收藏${MAX_COLLECT_COUNT}个站点`, icon: 'none' }); return false; } return true; }; const handleCollect = async (e: any) => { e.stopPropagation(); try { const type = isCollected ? 2 : 1; // 1=收藏 2=取消收藏 if (type === 1) { const canCollect = await checkCollectLimit(); if (!canCollect) { return; } } const payload: any = { itemType: 1, // 默认为站点处理 latitude: item.latitude, src/robotaxi/utils/car-control/RtAuthBluetoothManager.js
New file @@ -0,0 +1,432 @@ // RtAuthBluetoothManager.js // 精简版蓝牙管理器,只支持 RtAuth 鉴权模式(Robotaxi 场景) import Taro from '@tarojs/taro'; import { ab2hex, madAddressToLocalName, hexStringToArray } from '../../../utils/canControlTools'; // 引入日志和WASM模块 var log = require('./log.js'); const bleWasmModule = require('./bleMethod'); export default class RtAuthBluetoothManager { constructor(options) { // 回调函数 this.onStatusChange = options.onStatusChange || (() => {}); // 连接状态回调 this.onError = options.onError || (() => {}); // 错误回调 this.onRtAuthResult = options.onRtAuthResult || (() => {}); // RtAuth 鉴权结果回调 this.onRtNotification = options.onRtNotification || (() => {}); // RtNotification 通知回调 // 常量 this.serviceId = '0000ED28-ABAB-406D-832D-7AD799B479F7'; this.writeId = '0000ED28-ABAB-9C8C-2801-B1A015E46D6C'; this.MAX_RETRY_TIMES = 1; // 内部状态 this.state = { deviceId: '', macAddress: '', certificate: '', // 鉴权凭证 deviceModel: '', // 设备型号 isIOS: false, connected: false, isScaning: false, connectStatus: 0, // 0:断开, 1:连接中, 2:已连接, 3:已鉴权 tryTimes: 0, // 特征值 readServiceId: '', writeServiceId: '', readChs: '', writeChs: '', indecateChs: '', indecateServiceId: '' }; this.initSystemInfo(); } initSystemInfo() { Taro.getSystemInfo({ success: res => { this.state.isIOS = res.system.toLowerCase().indexOf('ios') >= 0; } }); } // 更新车辆配置(macAddress 必填,certificate 和 deviceModel 可选,用于自动鉴权) updateCarConfig(config) { this.state.macAddress = config.macAddress; this.state.certificate = config.certificate || ''; this.state.deviceModel = config.deviceModel || ''; this.resetState(); } resetState() { this.state.connectStatus = 0; this.state.connected = false; this.state.tryTimes = 0; this.notifyStatus(0); } notifyStatus(status) { this.state.connectStatus = status; this.onStatusChange(status); } // --- 蓝牙连接流程 --- openBluetoothAdapter() { if (this.state.connectStatus === 2) return; log.info('RtAuth: 准备初始化蓝牙...'); Taro.getLocation({ type: 'wgs84', success: () => this._realOpenAdapter(), fail: () => this._realOpenAdapter() }); } _realOpenAdapter() { wx.openBluetoothAdapter({ success: () => { log.info('RtAuth: 打开蓝牙适配器成功'); this.findAndConnect(); }, fail: (res) => { log.info('RtAuth: 打开蓝牙适配器失败', res); if (res.errMsg.includes('already opened')) { wx.closeBluetoothAdapter({ success: () => this._realOpenAdapter() }); } else { this.onError({ type: 'ADAPTER_ERR', msg: '请打开蓝牙和定位功能' }); } } }); } findAndConnect() { const { macAddress, isIOS } = this.state; let key = madAddressToLocalName(macAddress); if (isIOS) key = key + 'IOS'; const cacheDeviceId = Taro.getStorageSync(key); if (cacheDeviceId) { log.info('RtAuth: 使用缓存DeviceId连接', cacheDeviceId); this.state.deviceId = cacheDeviceId; this.connectDevice(); } else { this.startScan(); } } startScan() { if (this.state.isScaning) return; this.state.isScaning = true; wx.startBluetoothDevicesDiscovery({ allowDuplicatesKey: true, success: () => this.onBluetoothDeviceFound() }); } onBluetoothDeviceFound() { const { macAddress, isIOS } = this.state; const targetLocalName = madAddressToLocalName(macAddress); wx.onBluetoothDeviceFound((res) => { res.devices.forEach(device => { if (!device.name && !device.localName) return; if (device.localName === targetLocalName) { log.info('RtAuth: 扫描到目标设备', device.deviceId); const keyS = device.localName + (isIOS ? 'IOS' : ''); Taro.setStorage({ key: keyS, data: device.deviceId }); this.state.deviceId = device.deviceId; this.stopScan(); this.connectDevice(); } }); }); } stopScan() { wx.stopBluetoothDevicesDiscovery(); this.state.isScaning = false; } connectDevice() { if (this.state.connectStatus === 2) return; this.notifyStatus(1); log.info('RtAuth: 开始连接设备', this.state.deviceId); wx.createBLEConnection({ deviceId: this.state.deviceId, success: () => { log.info('RtAuth: 连接设备成功'); this.state.tryTimes = 0; this.state.connected = true; if (this.state.isScaning) this.stopScan(); this.getServices(); }, fail: (err) => { log.info('RtAuth: 连接失败', err); if (err.errno === 1509007 || err.errMsg.includes('already connected')) { this.state.tryTimes = 0; this.state.connected = true; if (this.state.isScaning) this.stopScan(); this.getServices(); return; } if (this.state.tryTimes < this.MAX_RETRY_TIMES) { this.state.tryTimes++; wx.closeBLEConnection({ deviceId: this.state.deviceId }); setTimeout(() => this.openBluetoothAdapter(), 1500); } else { this.notifyStatus(0); this.closeConnection(); this.onError({ type: 'CONNECT_ERR', msg: '蓝牙连接失败,请靠近车辆重试' }); } } }); } getServices() { wx.getBLEDeviceServices({ deviceId: this.state.deviceId, success: (res) => { const targetService = res.services.find(s => s.uuid === this.serviceId || s.uuid.toUpperCase() === this.serviceId ); if (targetService) { this.getCharacteristics(targetService.uuid); } } }); } getCharacteristics(serviceId) { wx.getBLEDeviceCharacteristics({ deviceId: this.state.deviceId, serviceId, success: (res) => { res.characteristics.forEach(item => { if (item.properties.read) { this.state.readChs = item.uuid; this.state.readServiceId = serviceId; } if (item.properties.write && item.uuid === this.writeId) { this.state.writeChs = item.uuid; this.state.writeServiceId = serviceId; } if (item.properties.indicate) { this.state.indecateChs = item.uuid; this.state.indecateServiceId = serviceId; this.setIndicateListener(); } }); } }); } setIndicateListener() { wx.notifyBLECharacteristicValueChange({ deviceId: this.state.deviceId, serviceId: this.state.indecateServiceId, characteristicId: this.state.indecateChs, state: true, success: () => { log.info('RtAuth: 蓝牙连接就绪'); this.notifyStatus(2); wx.onBLECharacteristicValueChange((characteristic) => { this.handleReceivedData(characteristic.value); }); // 如果已配置 certificate 和 deviceModel,自动发送鉴权 if (this.state.certificate && this.state.deviceModel) { log.info('RtAuth: 自动发送鉴权'); this.sendRtAuthData(this.state.certificate, this.state.deviceModel); } } }); this.listenConnectionState(); } listenConnectionState() { wx.onBLEConnectionStateChange((res) => { if (!res.connected && this.state.connected) { log.info('RtAuth: 蓝牙异常断开'); this.closeConnection(); } }); } closeConnection() { log.info('RtAuth: 断开连接'); this.stopScan(); if (this.state.connected) { wx.closeBLEConnection({ deviceId: this.state.deviceId }); } this.resetState(); } // --- 数据处理 --- handleReceivedData(value) { const dataHex = ab2hex(value); log.info('RtAuth: 收到数据', dataHex); // RtAuth 响应 (MsgId = 0x0B) if (dataHex.startsWith('0b') || dataHex.startsWith('0B')) { this.handleRtAuthResponse(dataHex); return; } // RtNotification (MsgId = 0x0C) if (dataHex.startsWith('0c') || dataHex.startsWith('0C')) { this.handleRtNotification(dataHex); return; } log.info('RtAuth: 忽略非 0x0B/0x0C 数据'); } handleRtAuthResponse(dataHex) { log.info('RtAuth: 解析 RtAuth 响应'); try { const wasmData = bleWasmModule.allocateUTF8(dataHex); const wasmResult = bleWasmModule._parseRtAuthResponse(wasmData, dataHex.length / 2); const resultStr = bleWasmModule.UTF8ToString(wasmResult); bleWasmModule._free(wasmData); log.info('RtAuth: 解析结果', resultStr); const result = JSON.parse(resultStr); if (result.error) { this.onRtAuthResult({ success: false, error: result.error }); } else if (result.rtAuthResult) { const isSuccess = result.rtAuthResult.status === 0; // 鉴权成功,更新状态为 3(已鉴权) if (isSuccess) { this.notifyStatus(3); } this.onRtAuthResult({ success: isSuccess, data: result.rtAuthResult }); } } catch (e) { log.info('RtAuth: 解析异常', e); this.onRtAuthResult({ success: false, error: e.message || e }); } } handleRtNotification(dataHex) { log.info('RtAuth: 解析 RtNotification'); try { const wasmData = bleWasmModule.allocateUTF8(dataHex); const wasmResult = bleWasmModule._parseRtNotification(wasmData, dataHex.length / 2); const resultStr = bleWasmModule.UTF8ToString(wasmResult); bleWasmModule._free(wasmData); log.info('RtAuth: 解析结果', resultStr); const result = JSON.parse(resultStr); if (result.error) { this.onRtNotification({ type: 'error', error: result.error }); } else if (result.rtNotification) { this.onRtNotification({ type: 'pollingUnlock', success: result.rtNotification.result === 0, data: result.rtNotification }); } } catch (e) { log.info('RtAuth: 解析异常', e); this.onRtNotification({ type: 'error', error: e.message || e }); } } // --- 发送数据 --- writeBLEData(data) { const buffer = Uint8Array.from(data).buffer; wx.writeBLECharacteristicValue({ deviceId: this.state.deviceId, serviceId: this.state.writeServiceId, characteristicId: this.state.writeChs, value: buffer, success: () => log.info('RtAuth: 发送成功'), fail: (err) => log.info('RtAuth: 发送失败', err) }); } // 发送 RtAuth 鉴权数据 sendRtAuthData(certificate, deviceModel) { log.info('RtAuth: 发送鉴权数据'); if (this.state.connectStatus !== 2) { this.onError({ type: 'RT_AUTH_ERR', msg: '蓝牙未连接' }); return false; } if (!certificate || !deviceModel) { this.onError({ type: 'RT_AUTH_ERR', msg: '缺少 certificate 或 deviceModel' }); return false; } try { // certificate: hex 字符串,长度除以 2 得到字节数 // deviceModel: 普通字符串,使用 lengthBytesUTF8 计算 UTF-8 编码后的字节长度 const deviceModelLength = bleWasmModule.lengthBytesUTF8(deviceModel); const wasmCertificate = bleWasmModule.allocateUTF8(certificate); const wasmDeviceModel = bleWasmModule.allocateUTF8(deviceModel); const wasmResult = bleWasmModule._generateRtAuthData( wasmCertificate, certificate.length / 2, // certificate 是 hex,字节数 = 长度/2 wasmDeviceModel, deviceModelLength // deviceModel 使用 UTF-8 字节长度 ); const rtAuthHex = bleWasmModule.UTF8ToString(wasmResult); bleWasmModule._free(wasmCertificate); bleWasmModule._free(wasmDeviceModel); log.info('RtAuth: 生成数据', rtAuthHex); this.writeBLEData(hexStringToArray(rtAuthHex)); return true; } catch (e) { log.info('RtAuth: 发送失败', e); this.onError({ type: 'RT_AUTH_ERR', msg: e.message || e }); return false; } } // 重试连接 retryConnection() { log.info('RtAuth: 重连'); this.closeConnection(); this.state.tryTimes = 0; setTimeout(() => { if (!this.state.macAddress) { this.onError({ type: 'CONFIG_ERR', msg: '缺少 macAddress' }); return; } this.openBluetoothAdapter(); }, 500); } } src/robotaxi/utils/car-control/bleMethod.js
New file Diff too large src/robotaxi/utils/car-control/bleMethod.wasm.brBinary files differ