xiaoZhiConnect.js 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167
  1. import qs from 'qs';
  2. // 导入我们之前改写的日志模块
  3. /**
  4. * 验证WebSocket服务器URL格式
  5. * @param {string} wsUrl
  6. * @returns {boolean}
  7. */
  8. function validateWsUrl(wsUrl) {
  9. if (!wsUrl) {
  10. console.log('WebSocket服务器地址不能为空', 'error');
  11. return false;
  12. }
  13. if (!wsUrl.startsWith('ws://') && !wsUrl.startsWith('wss://')) {
  14. console.log('URL格式错误,必须以ws://或wss://开头', 'error');
  15. return false;
  16. }
  17. return true;
  18. }
  19. /**
  20. * 验证设备配置
  21. * @param {object} config
  22. * @returns {boolean}
  23. */
  24. function validateConfig(config) {
  25. if (!config.deviceMac) {
  26. console.log('设备MAC地址不能为空', 'error');
  27. return false;
  28. }
  29. if (!config.clientId) {
  30. console.log('客户端ID不能为空', 'error');
  31. return false;
  32. }
  33. return true;
  34. }
  35. /**
  36. * 发送OTA请求,验证设备状态
  37. * @param {string} otaUrl
  38. * @param {object} config
  39. * @returns {Promise<boolean>} - 返回一个Promise,resolve为true表示成功,false表示失败
  40. */
  41. async function sendOTA(otaUrl, config) {
  42. console.log('正在进行OTA状态验证...', 'info');
  43. try {
  44. // 【修正】uni.request 的 await 返回的是单个 res 对象
  45. const res = await uni.request({
  46. url: otaUrl,
  47. method: 'POST',
  48. header: {
  49. 'Content-Type': 'application/json',
  50. 'Device-Id': config.deviceMac,
  51. 'Client-Id': config.clientId
  52. },
  53. data: {
  54. version: 0,
  55. uuid: '',
  56. application: {
  57. name: 'xiaozhi-uniapp-test',
  58. version: '1.0.0',
  59. compile_time: '2025-04-16 10:00:00',
  60. idf_version: '4.4.3',
  61. elf_sha256: '1234567890abcdef1234567890abcdef1234567890abcdef'
  62. },
  63. ota: { label: 'xiaozhi-uniapp-test' },
  64. board: {
  65. type: 'xiaozhi-uniapp-test',
  66. ssid: 'xiaozhi-uniapp-test',
  67. rssi: 0,
  68. channel: 0,
  69. ip: '192.168.1.1',
  70. mac: config.deviceMac
  71. },
  72. flash_size: 0,
  73. minimum_free_heap_size: 0,
  74. mac_address: config.deviceMac,
  75. chip_model_name: '',
  76. chip_info: { model: 0, cores: 0, revision: 0, features: 0 },
  77. partition_table: [{ label: '', type: 0, subtype: 0, address: 0, size: 0 }]
  78. }
  79. });
  80. console.log(res,'res');
  81. let _res = res[1]||null;
  82. if(!_res) return false;
  83. if (_res.statusCode >= 200 && _res.statusCode < 300) {
  84. // 【新增判断】检查返回的数据体中是否包含error字段
  85. if (_res.data && _res.data.error) {
  86. // 如果有error字段,说明业务逻辑失败了
  87. throw new Error(`OTA业务错误: ${_res.data.error}`);
  88. }
  89. // 只有当状态码正确且没有业务错误时,才算真正成功
  90. console.log(`OTA验证成功: ${JSON.stringify(_res.data)}`, 'success');
  91. return true;
  92. } else {
  93. throw new Error(`HTTP错误: ${_res.statusCode}`);
  94. }
  95. } catch (err) {
  96. const errorMessage = err.errMsg || err.message || '未知网络错误';
  97. console.log(`OTA验证失败: ${errorMessage}`, 'error');
  98. return false;
  99. }
  100. }
  101. /**
  102. * 【修正版 - 使用qs库】构建带参数的URL
  103. * @param {string} baseUrl
  104. * @param {object} params
  105. * @returns {string}
  106. */
  107. function buildUrlWithParams(baseUrl, params) {
  108. const paramString = qs.stringify(params, { addQueryPrefix: true }); // addQueryPrefix 会自动加上 '?'
  109. return baseUrl + paramString;
  110. }
  111. /**
  112. * 执行OTA检查并连接WebSocket服务器
  113. * @param {string} otaUrl - OTA服务器地址
  114. * @param {string} wsUrl - WebSocket服务器地址
  115. * @param {object} config - 设备配置对象
  116. * @returns {Promise<SocketTask|null>} - 返回一个Promise。
  117. * - 如果连接前置步骤成功,resolve为uniapp的SocketTask对象。
  118. * - 如果任何步骤失败,resolve为null。
  119. */
  120. export async function webSocketConnect(otaUrl, wsUrl, config) {
  121. if (!validateWsUrl(wsUrl) || !validateConfig(config)) {
  122. return null; // 验证失败
  123. }
  124. // 1. 执行OTA检查
  125. console.log(otaUrl, config);
  126. const otaOk = await sendOTA(otaUrl, config);
  127. console.log(otaOk);
  128. if (!otaOk) {
  129. // OTA失败,直接返回,由调用方处理UI状态
  130. return null;
  131. }
  132. // 2. 构建连接URL
  133. // 小程序环境没有内置URL对象,需要手动拼接
  134. const params = {
  135. 'device-id': config.deviceMac,
  136. 'client-id': config.clientId
  137. };
  138. const finalUrl = buildUrlWithParams(wsUrl, params);
  139. console.log(`准备连接WebSocket: ${finalUrl}`, 'info');
  140. // 3. 使用 uni.connectSocket 替换 new WebSocket()
  141. // 它会立即返回一个 SocketTask 对象,连接过程是异步的
  142. const socketTask = uni.connectSocket({
  143. url: finalUrl,
  144. // success/fail回调只代表API调用是否成功,不代表连接是否成功
  145. success: () => {},
  146. fail: (err) => {
  147. console.log(`uni.connectSocket API调用失败: ${JSON.stringify(err)}`, 'error');
  148. }
  149. });
  150. // 4. 返回 SocketTask 对象
  151. // 调用此函数的组件需要负责监听 onOpen, onMessage, onError, onClose 事件
  152. return socketTask;
  153. }