import request from '@/utils/request' import Common from '@/utils/Common.js' // ================== 蓝牙协议封装类 ================== class HBY100JProtocol { constructor() { this.deviceId = ''; // 4G 接口所需的 deviceId this.isBleConnected = false; this.bleDeviceId = ''; // 小程序/APP中连接蓝牙的 deviceId // 蓝牙服务与特征值 UUID this.SERVICE_UUID = '0000AE30-0000-1000-8000-00805F9B34FB'; // 0xAE30 this.WRITE_UUID = '0000AE03-0000-1000-8000-00805F9B34FB'; // 0xAE03 this.NOTIFY_UUID = '0000AE02-0000-1000-8000-00805F9B34FB'; // 0xAE02 this.onNotifyCallback = null; this._fileResponseResolve = null; // 文件上传时等待设备 FB 05 响应 } // 等待设备 FB 05 响应,超时后仍 resolve(设备可能不响应每包) waitForFileResponse(timeoutMs = 2000) { return new Promise((resolve) => { const timer = setTimeout(() => { if (this._fileResponseResolve) { this._fileResponseResolve = null; resolve(null); } }, timeoutMs); this._fileResponseResolve = (result) => { clearTimeout(timer); this._fileResponseResolve = null; resolve(result); }; }); } setBleConnectionStatus(status, bleDeviceId = '') { this.isBleConnected = status; if (bleDeviceId) { this.bleDeviceId = bleDeviceId; } } /** 协议单字节:界面常传字符串或 -1,必须转成 0~255 */ _u8(val, fallback = 0) { const n = Number(val); if (!Number.isFinite(n) || n < 0) return fallback & 0xFF; return n & 0xFF; } onNotify(callback) { this.onNotifyCallback = callback; } parseBleData(buffer) { const view = buffer instanceof Uint8Array ? buffer : new Uint8Array(buffer); if (view.length < 3) return null; const header = view[0]; const tail = view[view.length - 1]; // 5.1 连接蓝牙设备主动上报 MAC 地址: FC + 6字节MAC + FF if (header === 0xFC && tail === 0xFF && view.length >= 8) { const macBytes = view.slice(1, 7); const macAddress = Array.from(macBytes).map(b => b.toString(16).padStart(2, '0').toUpperCase()).join(':'); const result = { type: 'mac', macAddress }; console.log('[100J-蓝牙] 设备上报MAC:', macAddress, '原始:', Array.from(view).map(b => b.toString(16).padStart(2, '0').toUpperCase()).join(' ')); if (this.onNotifyCallback) this.onNotifyCallback(result); return result; } if (header !== 0xFB || tail !== 0xFF) return null; // 校验头尾 const funcCode = view[1]; const data = view.slice(2, view.length - 1); const hexStr = Array.from(view).map(b => b.toString(16).padStart(2, '0').toUpperCase()).join(' '); let result = { funcCode, rawData: data }; switch (funcCode) { case 0x01: result.resetType = data[0]; break; case 0x02: break; case 0x03: // 5.4 获取设备位置:经度8B+纬度8B 均为 float64,设备主动上报(1分钟)与主动查询响应格式相同 if (data.length >= 16) { const lonBuf = new ArrayBuffer(8); const latBuf = new ArrayBuffer(8); new Uint8Array(lonBuf).set(data.slice(0, 8)); new Uint8Array(latBuf).set(data.slice(8, 16)); result.longitude = new DataView(lonBuf).getFloat64(0, true); result.latitude = new DataView(latBuf).getFloat64(0, true); } break; case 0x05: // 05: 文件更新响应 FB 05 [fileType] [status] FF,status: 1=成功 2=失败 if (data.length >= 1) result.fileType = data[0]; if (data.length >= 2) result.fileStatus = data[1]; // 1=Success, 2=Failure if (this._fileResponseResolve) this._fileResponseResolve(result); break; case 0x04: // 5.5 获取设备电源状态: 电池容量8B + 电压8B + 百分比1B + 车载电源1B + 续航时间2B(分钟) if (data.length >= 20) { result.batteryPercentage = data[16]; result.vehiclePower = data[17]; result.batteryRemainingTime = data[18] | (data[19] << 8); // 小端序,单位分钟 } break; case 0x06: // 06: 语音播报响应 result.voiceBroadcast = data[0]; break; case 0x09: // 09: 修改音量响应 result.volume = data[0]; break; case 0x0A: // 0A: 爆闪模式响应 result.strobeEnable = data[0]; result.strobeMode = data[1]; break; case 0x0B: // 0B: 修改警示灯爆闪频率响应 result.strobeFrequency = data[0]; break; case 0x0C: // 0C: 强制声光报警响应 result.alarmEnable = data[0]; result.alarmMode = data[1]; break; case 0x0D: // 0D: 警示灯 LED 亮度调节响应 result.redBrightness = data[0]; result.blueBrightness = data[1]; result.yellowBrightness = data[2]; break; case 0x0E: // 0E: 获取当前工作方式响应 result.voiceBroadcast = data[0]; result.alarmEnable = data[1]; result.alarmMode = data[2]; result.strobeEnable = data[3]; result.strobeMode = data[4]; result.strobeFrequency = data[5]; result.volume = data[6]; result.redBrightness = data[7]; result.blueBrightness = data[8]; result.yellowBrightness = data[9]; break; } const funcNames = { 0x01: '复位', 0x02: '基础信息', 0x03: '位置', 0x04: '电源状态', 0x05: '文件更新', 0x06: '语音播报', 0x09: '音量', 0x0A: '爆闪模式', 0x0B: '爆闪频率', 0x0C: '强制报警', 0x0D: 'LED亮度', 0x0E: '工作方式' }; const name = funcNames[funcCode] || ('0x' + funcCode.toString(16)); console.log('[100J-蓝牙] 设备响应 FB:', name, '解析:', JSON.stringify(result), '原始:', hexStr); if (this.onNotifyCallback) { this.onNotifyCallback(result); } return result; } sendBleData(funcCode, dataBytes = []) { return new Promise((resolve, reject) => { if (!this.isBleConnected || !this.bleDeviceId) { return reject(new Error('蓝牙未连接')); } const buffer = new ArrayBuffer(dataBytes.length + 3); const view = new Uint8Array(buffer); view[0] = 0xFA; // 数据头 view[1] = funcCode; // 功能码 for (let i = 0; i < dataBytes.length; i++) { view[2 + i] = dataBytes[i]; } view[view.length - 1] = 0xFF; // 结尾 const sendHex = Array.from(view).map(b => b.toString(16).padStart(2, '0').toUpperCase()).join(' '); console.log('[100J-蓝牙] 下发指令 FA:', '0x' + funcCode.toString(16).toUpperCase(), sendHex); // 使用项目中统一的 BleHelper 发送数据 import('@/utils/BleHelper.js').then(module => { const bleTool = module.default.getBleTool(); bleTool.sendData(this.bleDeviceId, buffer, this.SERVICE_UUID, this.WRITE_UUID) .then(res => resolve(res)) .catch(err => reject(err)); }); }); } // 纯蓝牙指令发送方法 deviceReset(type = 0) { return this.sendBleData(0x01, [type]); } getBasicInfo() { return this.sendBleData(0x02, []); } getLocation() { return this.sendBleData(0x03, []); } getPowerStatus() { return this.sendBleData(0x04, []); } setVoiceBroadcast(enable) { return this.sendBleData(0x06, [this._u8(enable)]); } setVolume(volume) { return this.sendBleData(0x09, [this._u8(volume)]); } setStrobeMode(enable, mode) { return this.sendBleData(0x0A, [this._u8(enable), this._u8(mode)]); } setStrobeFrequency(frequency) { return this.sendBleData(0x0B, [this._u8(frequency)]); } setForceAlarm(enable, mode) { return this.sendBleData(0x0C, [this._u8(enable), this._u8(mode)]); } setLightBrightness(red, blue = 0, yellow = 0) { return this.sendBleData(0x0D, [this._u8(red), this._u8(blue), this._u8(yellow)]); } getCurrentWorkMode() { return this.sendBleData(0x0E, []); } // 0x05 文件上传:分片传输,协议 FA 05 [fileType] [phase] [data...] FF // fileType: 1=语音 2=图片 3=动图 4=OTA // phase: 0=开始 1=数据 2=结束 // 每包最大字节 蓝牙:CHUNK_SIZE=500 // 支持 fileUrl(需网络下载) 或 localPath(无网络时本地文件) uploadVoiceFileBle(fileUrlOrLocalPath, fileType = 1, onProgress) { const CHUNK_SIZE = 500; // 每包有效数据,参考 6155 deviceDetail.vue return new Promise((resolve, reject) => { if (!this.isBleConnected || !this.bleDeviceId) { return reject(new Error('蓝牙未连接')); } if (!fileUrlOrLocalPath) { return reject(new Error('缺少文件地址或本地路径')); } const isLocalPath = !/^https?:\/\//i.test(fileUrlOrLocalPath); if (onProgress) onProgress(1); const readFromPath = (path) => { const doSend = (bytes) => { this._sendVoiceChunks(bytes, fileType, CHUNK_SIZE, onProgress) .then(resolve).catch(reject); }; // App 端 getFileSystemManager 未实现,直接用 plus.io.requestFileSystem+getFile readFromPathPlus(path, doSend, reject); }; const readFileEntry = (entry, doSend, reject) => { entry.file((file) => { const reader = new plus.io.FileReader(); reader.onloadend = (e) => { try { const buf = e.target.result; const bytes = new Uint8Array(buf); doSend(bytes); } catch (err) { console.error('[100J-蓝牙] 读取ArrayBuffer异常:', err); reject(err); } }; reader.onerror = () => reject(new Error('读取文件失败')); reader.readAsArrayBuffer(file); }, (err) => reject(err)); }; const readFromPathPlus = (path, doSend, reject) => { if (typeof plus === 'undefined' || !plus.io) { console.error('[100J-蓝牙] 当前环境不支持文件读取(plus.io)'); reject(new Error('当前环境不支持文件读取')); return; } // _downloads/ 用 requestFileSystem+getFile(避免 resolveLocalFileSystemURL 卡住) if (path && path.startsWith('_downloads/')) { const fileName = path.replace(/^_downloads\//, ''); plus.io.requestFileSystem(plus.io.PUBLIC_DOWNLOADS, (fs) => { fs.root.getFile(fileName, {}, (entry) => readFileEntry(entry, doSend, reject), (err) => reject(err)); }, (err) => reject(err)); return; } // _doc/ 用 requestFileSystem(PRIVATE_DOC),逐级 getDirectory 再 getFile(嵌套路径兼容) if (path && path.startsWith('_doc/')) { const relPath = path.replace(/^_doc\//, ''); const parts = relPath.split('/'); const fileName = parts.pop(); const dirs = parts; plus.io.requestFileSystem(plus.io.PRIVATE_DOC, (fs) => { let cur = fs.root; const next = (i) => { if (i >= dirs.length) { cur.getFile(fileName, { create: false }, (entry) => readFileEntry(entry, doSend, reject), (err) => reject(err)); return; } cur.getDirectory(dirs[i], { create: false }, (dir) => { cur = dir; next(i + 1); }, (err) => reject(err)); }; next(0); }, (err) => reject(err)); return; } // 其他路径兜底 let resolvePath = path; if (path && path.startsWith('/') && !path.startsWith('file://')) resolvePath = 'file://' + path; plus.io.resolveLocalFileSystemURL(resolvePath, (entry) => readFileEntry(entry, doSend, reject), (err) => reject(err)); }; if (isLocalPath) { // 本地路径:无网络时直接读取 readFromPath(fileUrlOrLocalPath); } else { // 网络 URL:优先用 uni.request 拉取;加超时避免断网时进度长期卡在 1%~2% let fetchUrl = fileUrlOrLocalPath; if (fetchUrl.startsWith('http://')) fetchUrl = 'https://' + fetchUrl.slice(7); if (onProgress) onProgress(2); const fallbackDownload = () => { uni.downloadFile({ url: fetchUrl, success: (res) => { if (res.statusCode !== 200 || !res.tempFilePath) { if (onProgress) onProgress(0); reject(new Error('下载失败: ' + (res.statusCode || '无路径'))); return; } Common.moveFileToDownloads(res.tempFilePath).then((p) => readFromPath(p)).catch(() => readFromPath(res.tempFilePath)); }, fail: (err) => { if (onProgress) onProgress(0); reject(err || new Error('下载失败')); } }); }; const reqTimeoutMs = 20000; const reqPromise = new Promise((resolveReq, rejectReq) => { uni.request({ url: fetchUrl, method: 'GET', responseType: 'arraybuffer', timeout: reqTimeoutMs, success: (res) => resolveReq(res), fail: (e) => rejectReq(e || new Error('请求失败')) }); }); const timeoutPromise = new Promise((_, rejectT) => { setTimeout(() => rejectT(new Error('拉取语音超时')), reqTimeoutMs + 2000); }); Promise.race([reqPromise, timeoutPromise]) .then((res) => { if (res.statusCode === 200 && res.data) { const bytes = res.data instanceof ArrayBuffer ? new Uint8Array(res.data) : new Uint8Array(res.data || []); if (bytes.length > 0) { const doSend = (b) => { this._sendVoiceChunks(b, fileType, CHUNK_SIZE, onProgress).then(resolve).catch(reject); }; doSend(bytes); return; } } fallbackDownload(); }) .catch(() => fallbackDownload()); } }); } _sendVoiceChunks(bytes, fileType, chunkSize, onProgress) { const total = bytes.length; const ft = (fileType & 0xFF) || 1; const DELAY_AFTER_START = 80; // 开始包后、等设备响应后再发的缓冲(ms) const DELAY_PACKET = 80; // 数据包间延时(ms),参考6155 const toHex = (arr) => Array.from(arr).map(b => b.toString(16).padStart(2, '0').toUpperCase()).join(' '); console.log('[100J-蓝牙] 语音下发总大小:', total, '字节, fileType=', ft); if (onProgress) onProgress(1); const bleToolPromise = import('@/utils/BleHelper.js').then(m => m.default.getBleTool()); let bleRef = null; const send = (dataBytes, label = '') => { const buf = new ArrayBuffer(dataBytes.length + 3); const v = new Uint8Array(buf); v[0] = 0xFA; v[1] = 0x05; for (let i = 0; i < dataBytes.length; i++) v[2 + i] = dataBytes[i]; v[v.length - 1] = 0xFF; const hex = toHex(v); const preview = v.length <= 32 ? hex : hex.slice(0, 96) + '...'; console.log(`[100J-蓝牙] 下发${label} 共${v.length}字节:`, preview); return bleToolPromise.then(ble => ble.sendData(this.bleDeviceId, buf, this.SERVICE_UUID, this.WRITE_UUID)); }; const delay = (ms) => new Promise(r => setTimeout(r, ms)); // 开始包: FA 05 [fileType] [phase=0] [size 4B LE] FF const startData = [ft, 0, total & 0xFF, (total >> 8) & 0xFF, (total >> 16) & 0xFF, (total >> 24) & 0xFF]; const waitPromise = this.waitForFileResponse(1000); return bleToolPromise.then(ble => { bleRef = ble; ble.setVoiceUploading(true); return send(startData, ' 开始包') .then(() => { if (onProgress) onProgress(3); return waitPromise; }) .then(() => { if (onProgress) onProgress(5); return delay(DELAY_AFTER_START); }) .then(() => { let seq = 0; const sendNext = (offset) => { if (offset >= total) { return delay(DELAY_PACKET).then(() => send([ft, 2], ' 结束包')); } const chunk = bytes.slice(offset, Math.min(offset + chunkSize, total)); const chunkData = [ft, 1, seq & 0xFF, (seq >> 8) & 0xFF, ...chunk]; return send(chunkData, ` #${seq} 数据包`).then(() => { seq++; if (onProgress) onProgress(Math.min(100, Math.floor((offset + chunk.length) / total * 100))); return delay(DELAY_PACKET).then(() => sendNext(offset + chunk.length)); }); }; return sendNext(0); }) .then(() => { if (onProgress) onProgress(100); return { code: 200, msg: '语音文件已通过蓝牙上传' }; }); }).finally(() => { if (bleRef) bleRef.setVoiceUploading(false); }); } } // ================== 全局单例与状态管理 ================== const protocolInstance = new HBY100JProtocol(); // 暴露给页面:更新蓝牙连接状态 export function updateBleStatus(isConnected, bleDeviceId, deviceId) { protocolInstance.setBleConnectionStatus(isConnected, bleDeviceId); protocolInstance.deviceId = deviceId; console.log('[100J] 蓝牙状态:', isConnected ? '已连接(后续指令走蓝牙)' : '已断开(后续指令走4G)', { bleDeviceId: bleDeviceId || '-', deviceId }); } // 暴露给页面:获取当前蓝牙连接状态(用于跨页面传递,确保语音管理等子页走蓝牙优先) export function getBleStatus() { return { isConnected: protocolInstance.isBleConnected, bleDeviceId: protocolInstance.bleDeviceId, deviceId: protocolInstance.deviceId }; } // 暴露给页面:解析蓝牙接收到的数据 export function parseBleData(buffer) { return protocolInstance.parseBleData(buffer); } // 暴露给页面:蓝牙连接后主动拉取电源状态(电量、续航) export function fetchBlePowerStatus() { if (!protocolInstance.isBleConnected) return Promise.reject(new Error('蓝牙未连接')); console.log('[100J-蓝牙] 拉取电源状态 已通过蓝牙发送 FA 04 FF'); return protocolInstance.getPowerStatus(); } // 暴露给页面:蓝牙连接后主动拉取定位(优先蓝牙,设备也会每1分钟主动上报) export function fetchBleLocation() { if (!protocolInstance.isBleConnected) return Promise.reject(new Error('蓝牙未连接')); console.log('[100J-蓝牙] 拉取定位 已通过蓝牙发送 FA 03 FF'); return protocolInstance.getLocation(); } // 等待蓝牙连接(扫描中时轮询,设备页可能在后台完成连接,100J 扫描约 15s) function waitForBleConnection(maxWaitMs = 12000, intervalMs = 500) { if (protocolInstance.isBleConnected && protocolInstance.bleDeviceId) return Promise.resolve(true); return new Promise((resolve) => { const start = Date.now(); const tick = () => { if (protocolInstance.isBleConnected && protocolInstance.bleDeviceId) { console.log('[100J] 等待蓝牙连接成功'); resolve(true); return; } if (Date.now() - start >= maxWaitMs) { console.log('[100J] 等待蓝牙连接超时,将走4G'); resolve(false); return; } setTimeout(tick, intervalMs); }; console.log('[100J] 蓝牙未连接,等待扫描/连接中...', maxWaitMs, 'ms'); tick(); }); } // 暴露给页面:尝试重连蓝牙(优先策略:断线后发指令前先尝试重连) export function tryReconnectBle(timeoutMs = 2500) { if (protocolInstance.isBleConnected) return Promise.resolve(true); if (!protocolInstance.bleDeviceId) return Promise.resolve(false); return new Promise((resolve) => { import('@/utils/BleHelper.js').then(module => { const bleTool = module.default.getBleTool(); const deviceId = protocolInstance.bleDeviceId; const f = bleTool.data.LinkedList.find(v => v.deviceId === deviceId); if (!f) { resolve(false); return; } const svc = f.writeServiceId || '0000AE30-0000-1000-8000-00805F9B34FB'; const write = f.wirteCharactId || '0000AE03-0000-1000-8000-00805F9B34FB'; const notify = f.notifyCharactId || '0000AE02-0000-1000-8000-00805F9B34FB'; const timer = setTimeout(() => { resolve(protocolInstance.isBleConnected); }, timeoutMs); console.log('[100J] 蓝牙优先:尝试重连', deviceId); bleTool.LinkBlue(deviceId, svc, write, notify, 1).then(() => { clearTimeout(timer); protocolInstance.setBleConnectionStatus(true, deviceId); console.log('[100J] 蓝牙重连成功'); resolve(true); }).catch(() => { clearTimeout(timer); resolve(false); }); }); }); } // ================== API 接口 (拦截层) ================== // 获取语音管理列表 export function deviceVoliceList(params) { return request({ url: `/app/video/queryAudioFileList`, method: 'get', data:params }) } // 重命名 export function videRenameAudioFile(data) { return request({ url: `/app/video/renameAudioFile`, method: 'post', data:data }) } // 删除语音文件列表 export function deviceDeleteAudioFile(params) { return request({ url: `/app/video/deleteAudioFile`, method: 'get', data:params }) } // 更新语音/使用语音:蓝牙优先,4G 兜底(不影响原有 4G 音频下发) // 有 fileUrl 或 localPath 且蓝牙可用时走蓝牙;否则或蓝牙失败时走 4G(与原先逻辑一致) export function deviceUpdateVoice(data) { const httpExec = () => request({ url: `/app/hby100j/device/updateVoice`, method: 'post', data: { id: data.id } }); const localPath = data.localPath; const fileUrl = data.fileUrl; const hasLocalPath = localPath && typeof localPath === 'string' && localPath.length > 0; const hasFileUrl = fileUrl && typeof fileUrl === 'string' && fileUrl.length > 0; const fileSource = hasLocalPath ? localPath : (hasFileUrl ? fileUrl : null); if (!fileSource) { console.log('[100J] 语音上传:无 fileUrl/localPath,走 4G'); return httpExec(); // 无文件源:直接 4G(原有逻辑) } console.log('[100J] 语音上传:有文件源,蓝牙优先', { isBleConnected: protocolInstance.isBleConnected, bleDeviceId: protocolInstance.bleDeviceId || '-' }); const bleExec = () => protocolInstance.uploadVoiceFileBle(fileSource, 1, data.onProgress); return execWithBleFirst(bleExec, httpExec, '语音文件上传', data.onWaiting); } // 100J信息 export function deviceDetail(id) { return request({ url: `/app/hby100j/device/${id}`, method: 'get', }) } // 蓝牙优先、4G 兜底:未连接时先等待扫描/连接,再尝试重连;蓝牙失败时回退 4G function execWithBleFirst(bleExec, httpExec, logName, onWaiting) { const doBle = () => bleExec().then(res => ({ ...(res || {}), _channel: 'ble' })); const do4G = () => httpExec().then(res => { res._channel = '4g'; return res; }); if (protocolInstance.isBleConnected && protocolInstance.bleDeviceId) { return doBle().catch(() => { console.log('[100J] 蓝牙失败,回退4G'); return do4G(); }); } // 无 bleDeviceId 时:可能扫描中,先等待连接(设备页在后台可能完成连接) if (!protocolInstance.bleDeviceId) { if (typeof onWaiting === 'function') onWaiting(); return waitForBleConnection(12000).then(connected => { return connected ? doBle().catch(() => { console.log('[100J] 蓝牙失败,回退4G'); return do4G(); }) : do4G(); }); } return tryReconnectBle(2500).then(reconnected => { return reconnected ? doBle().catch(() => { console.log('[100J] 蓝牙失败,回退4G'); return do4G(); }) : do4G(); }); } // 爆闪模式 export function deviceStrobeMode(data) { return execWithBleFirst( () => protocolInstance.setStrobeMode(data.enable, data.mode).then(() => ({ code: 200, msg: '操作成功(蓝牙)' })), () => request({ url: `/app/hby100j/device/strobeMode`, method: 'post', data }), '爆闪模式' ); } // 强制报警 export function deviceForceAlarmActivation(data) { return execWithBleFirst( () => protocolInstance.setForceAlarm(data.voiceStrobeAlarm, data.mode).then(() => ({ code: 200, msg: '操作成功(蓝牙)' })), () => request({ url: `/app/hby100j/device/forceAlarmActivation`, method: 'post', data }), '强制报警' ); } // 爆闪频率 export function deviceStrobeFrequency(data) { return execWithBleFirst( () => protocolInstance.setStrobeFrequency(data.frequency).then(() => ({ code: 200, msg: '操作成功(蓝牙)' })), () => request({ url: `/app/hby100j/device/strobeFrequency`, method: 'post', data }), '爆闪频率' ); } // 灯光调节亮度 export function deviceLightAdjustment(data) { return execWithBleFirst( () => protocolInstance.setLightBrightness(data.brightness, data.brightness, data.brightness).then(() => ({ code: 200, msg: '操作成功(蓝牙)' })), () => request({ url: `/app/hby100j/device/lightAdjustment`, method: 'post', data }), '灯光亮度' ); } // 调节音量 export function deviceUpdateVolume(data) { return execWithBleFirst( () => protocolInstance.setVolume(data.volume).then(() => ({ code: 200, msg: '操作成功(蓝牙)' })), () => request({ url: `/app/hby100j/device/updateVolume`, method: 'post', data }), '调节音量' ); } // 语音播放 export function deviceVoiceBroadcast(data) { return execWithBleFirst( () => protocolInstance.setVoiceBroadcast(data.voiceBroadcast).then(() => ({ code: 200, msg: '操作成功(蓝牙)' })), () => request({ url: `/app/hby100j/device/voiceBroadcast`, method: 'post', data }), '语音播报' ); }