// 全局状态变量 let tempTimer = 0; let client = null; let util = null; let mDeviceEvent = null; let crypto = null; let md5 = null; let aesjs = null; const timeOut = 20; // 超时时间 let timeId = ""; let sequenceControl = 0; // let sequenceNumber = -1; // 此变量在代码中未被使用,可以移除 // _self 对象用于维护单个连接会话的状态 let _self = { data: { deviceId: null, isConnected: false, failure: false, value: 0, desc: "请耐心等待...", isChecksum: true, isEncrypt: true, flagEnd: false, defaultData: 1, ssidType: 2, passwordType: 3, meshIdType: 3, ssid: "", password: "", meshId: "", uuid: "", serviceId: "", processList: [], result: [], service_uuid: "0000FFFF-0000-1000-8000-00805F9B34FB", characteristic_write_uuid: "0000FF01-0000-1000-8000-00805F9B34FB", characteristic_read_uuid: "0000FF02-0000-1000-8000-00805F9B34FB", customData: null, md5Key: 0, } }; // ================================================================= // 辅助/工具函数 (已优化) // ================================================================= /** * 将 ArrayBuffer 转换为十六进制字符串 * @param {ArrayBuffer} buffer * @returns {string} */ function buf2hex(buffer) { return Array.prototype.map.call(new Uint8Array(buffer), x => ('00' + x.toString(16)).slice(-2)).join(''); } /** * [优化] 将 ArrayBuffer (字节数组) 转换为字符串。 * 使用 TextDecoder API,性能更高且能正确处理 UTF-8 编码。 * @param {ArrayBuffer} buffer * @returns {string} */ function buf2string(buffer) { try { const dataView = new DataView(buffer); // 微信小程序环境支持 TextDecoder const decoder = new TextDecoder('utf-8'); return decoder.decode(dataView); } catch (e) { // 降级处理,以防极旧环境不支持 const arr = Array.prototype.map.call(new Uint8Array(buffer), x => x); let str = ''; for (let i = 0; i < arr.length; i++) { str += String.fromCharCode(arr[i]); } return decodeURIComponent(escape(str)); } } /** * [优化] 将字符串转换为 UTF-8 编码的字节数组。 * 使用 TextEncoder API,性能远超手动循环,且编码更标准。 * @param {string} str * @returns {number[]} */ function stringToUtf8Bytes(str) { try { // 微信小程序环境支持 TextEncoder const encoder = new TextEncoder(); return Array.from(encoder.encode(str)); } catch (e) { // 降级处理 return unescape(encodeURIComponent(str)).split("").map(val => val.charCodeAt()); } } /** * [优化] 判断并处理返回的数据帧,提取负载数据。 * 简化了原有的 if/else 重复逻辑。 * @param {string[]} fragNum - 帧控制字段的二进制数组 * @param {number[]} list - 原始数据包 * @param {any} md5Key - MD5密钥 * @returns {number[]} - 提取出的负载数据 */ function parsePayload(fragNum, list, md5Key) { let payload = list; // 步骤1: 如果有校验和,则移除末尾的校验位 if (fragNum[6] == "1") { payload = payload.slice(0, payload.length - 2); } // 步骤2: 根据是否加密进行解密 (当前代码中解密逻辑未实现,仅预留位置) if (fragNum[7] == "1") { // 返回数据已加密 // let iv = this.generateAESIV(parseInt(payload[2], 16)); // 在此处添加解密逻辑... } // 步骤3: 根据是否分包,移除帧头,提取有效数据 if (fragNum[3] == "0") { // 未分包 _self.data.flagEnd = true; return payload.slice(4); } else { // 分包 _self.data.flagEnd = false; return payload.slice(6); } } // ================================================================= // BLE 核心写入函数 // ================================================================= /** * 发送安全协商数据 (P, G, K) * @param {string} deviceId * @param {string} serviceId * @param {string} characteristicId * @param {object} client - DH 客户端实例 * @param {number[]} kBytes - 公钥 * @param {number[]} pBytes - P * @param {number[]} gBytes - G * @param {number[]|null} data - 分包后的剩余数据 */ function getSecret(deviceId, serviceId, characteristicId, client, kBytes, pBytes, gBytes, data) { let obj = {}; let frameControl = 0; sequenceControl = parseInt(sequenceControl) + 1; if (!util._isEmpty(data)) { obj = util.isSubcontractor(data, true, sequenceControl); frameControl = util.getFrameCTRLValue(false, true, util.DIRECTION_OUTPUT, false, obj.flag); } else { let payload = []; payload.push(util.NEG_SET_SEC_ALL_DATA); const pLength = pBytes.length; payload.push((pLength >> 8) & 0xff, pLength & 0xff, ...pBytes); const gLength = gBytes.length; payload.push((gLength >> 8) & 0xff, gLength & 0xff, ...gBytes); const kLength = kBytes.length; payload.push((kLength >> 8) & 0xff, kLength & 0xff, ...kBytes); obj = util.isSubcontractor(payload, true, sequenceControl); frameControl = util.getFrameCTRLValue(false, true, util.DIRECTION_OUTPUT, false, obj.flag); } const value = util.writeData(util.PACKAGE_VALUE, util.SUBTYPE_NEG, frameControl, sequenceControl, obj.len, obj.lenData); const typedArray = new Uint8Array(value); wx.writeBLECharacteristicValue({ deviceId: deviceId, serviceId: serviceId, characteristicId: characteristicId, value: typedArray.buffer, success: function (res) { if (obj.flag) { // 如果有剩余数据,则继续发送 getSecret(deviceId, serviceId, characteristicId, client, kBytes, pBytes, gBytes, obj.laveData); } }, fail: function (res) { console.error("getSecret failed:", res, { deviceId, serviceId, characteristicId }); } }); } /** * [新增] 通用的数据包发送函数,用于取代原先多个重复的 writeXXX 函数 * @param {number} subType - 包的子类型 * @param {number[]} dataPayload - 要发送的原始数据负载 * @param {function} onSuccess - 发送成功(且无分包)后的回调函数 */ function sendBlufiDataPacket(subType, dataPayload, onSuccess) { const deviceId = _self.data.deviceId; const serviceId = _self.data.service_uuid; const characteristicId = _self.data.characteristic_write_uuid; function writePacket(remainingData) { sequenceControl = parseInt(sequenceControl) + 1; // 检查是否需要分包 const obj = util.isSubcontractor(remainingData, _self.data.isChecksum, sequenceControl, _self.data.isEncrypt); const frameControl = util.getFrameCTRLValue(_self.data.isEncrypt, _self.data.isChecksum, util.DIRECTION_OUTPUT, false, obj.flag); // 加密数据 const encryptedData = util.encrypt(aesjs, _self.data.md5Key, sequenceControl, obj.lenData, true); // 组装最终数据包 const value = util.writeData(util.PACKAGE_VALUE, subType, frameControl, sequenceControl, obj.len, encryptedData); const typedArray = new Uint8Array(value); wx.writeBLECharacteristicValue({ deviceId: deviceId, serviceId: serviceId, characteristicId: characteristicId, value: typedArray.buffer, success: function (res) { if (obj.flag) { // 如果有分包,继续发送剩余数据 writePacket(obj.laveData); } else if (onSuccess) { // 发送完成,执行成功回调 onSuccess(); } }, fail: function (res) { console.error(`sendBlufiDataPacket failed for subType ${subType}:`, res); } }); } // 启动第一次写入 writePacket(dataPayload); } function writeDeviceRouterInfoStart() { const payload = [_self.data.defaultData]; const onSuccess = () => writeRouterSsid(); // 注意:此处的 subType 需要根据你的 util 库定义来确定 sendBlufiDataPacket(util.SUBTYPE_WIFI_MODEl, payload, onSuccess); } function writeRouterSsid() { const ssidData = stringToUtf8Bytes(_self.data.ssid); const onSuccess = () => writeDevicePwd(); sendBlufiDataPacket(util.SUBTYPE_SET_SSID, ssidData, onSuccess); } function writeDevicePwd() { const pwdData = stringToUtf8Bytes(_self.data.password); const onSuccess = () => writeDeviceEnd(); sendBlufiDataPacket(util.SUBTYPE_SET_PWD, pwdData, onSuccess); } function writeCutomsData() { const customDataBytes = stringToUtf8Bytes(_self.data.customData); // 自定义数据发送后通常没有下一步,所以 onSuccess 为 null sendBlufiDataPacket(util.SUBTYPE_CUSTOM_DATA, customDataBytes, null); } /** * 发送结束指令 */ function writeDeviceEnd() { sequenceControl = parseInt(sequenceControl) + 1; const frameControl = util.getFrameCTRLValue(_self.data.isEncrypt, false, util.DIRECTION_OUTPUT, false, false); const value = util.writeData(_self.data.PACKAGE_CONTROL_VALUE, util.SUBTYPE_END, frameControl, sequenceControl, 0, null); const typedArray = new Uint8Array(value); wx.writeBLECharacteristicValue({ deviceId: _self.data.deviceId, serviceId: _self.data.service_uuid, characteristicId: _self.data.characteristic_write_uuid, value: typedArray.buffer, success: function (res) {}, fail: function (res) {} }); } /** * 发送获取附近Wi-Fi列表的指令 */ function writeGetNearRouterSsid() { const deviceId = _self.data.deviceId; const serviceId = _self.data.service_uuid; const characteristicId = _self.data.characteristic_write_uuid; sequenceControl = parseInt(sequenceControl) + 1; const frameControl = util.getFrameCTRLValue(_self.data.isEncrypt, false, util.DIRECTION_OUTPUT, false, false); const value = util.writeData(util.PACKAGE_CONTROL_VALUE, util.SUBTYPE_WIFI_NEG, frameControl, sequenceControl, 0, null); const typedArray = new Uint8Array(value); wx.writeBLECharacteristicValue({ deviceId: deviceId, serviceId: serviceId, characteristicId: characteristicId, value: typedArray.buffer, success: function (res) {}, fail: function (res) {} }); } // ================================================================= // 事件监听与主逻辑 // ================================================================= function init() { // 加载依赖 mDeviceEvent = require('./xBlufi.js'); util = require('@/utils/blufi/util.js'); crypto = require('@/utils/blufi/crypto/crypto-dh.js'); md5 = require('@/utils/blufi/crypto/md5.min.js'); aesjs = require('@/utils/blufi/crypto/aes.js'); // 监听蓝牙连接状态变化 wx.onBLEConnectionStateChange(function (res) { const obj = { 'type': mDeviceEvent.XBLUFI_TYPE.TYPE_STATUS_CONNECTED, 'result': res.connected, 'data': res }; mDeviceEvent.notifyDeviceMsgEvent(obj); }); // 监听开始扫描蓝牙设备事件 mDeviceEvent.listenStartDiscoverBle(true, function (options) { if (options.isStart) { wx.closeBluetoothAdapter({ complete: function (res) { wx.openBluetoothAdapter({ success: function (res) { wx.startBluetoothDevicesDiscovery({ allowDuplicatesKey: true, success: function (res) { const devicesList = []; const foundDevices = new Set(); // 使用 Set 来快速去重 mDeviceEvent.notifyDeviceMsgEvent({ 'type': mDeviceEvent.XBLUFI_TYPE.TYPE_GET_DEVICE_LISTS_START, 'result': true, 'data': res }); wx.onBluetoothDeviceFound(function (deviceRes) { const device = deviceRes.devices[0]; if (device && device.deviceId && !foundDevices.has(device.deviceId)) { foundDevices.add(device.deviceId); devicesList.push({ ...device, advertisData: device.advertisData ? buf2hex(device.advertisData) : '' }); mDeviceEvent.notifyDeviceMsgEvent({ 'type': mDeviceEvent.XBLUFI_TYPE.TYPE_GET_DEVICE_LISTS, 'result': true, 'data': [...devicesList] // 发送列表的副本 }); } }); }, fail: function (res) { mDeviceEvent.notifyDeviceMsgEvent({ 'type': mDeviceEvent.XBLUFI_TYPE.TYPE_GET_DEVICE_LISTS_START, 'result': false, 'data': res }); } }); }, fail: function (res) { mDeviceEvent.notifyDeviceMsgEvent({ 'type': mDeviceEvent.XBLUFI_TYPE.TYPE_GET_DEVICE_LISTS_START, 'result': false, 'data': res }); } }); } }); } else { wx.stopBluetoothDevicesDiscovery({ success: function (res) { mDeviceEvent.notifyDeviceMsgEvent({ 'type': mDeviceEvent.XBLUFI_TYPE.TYPE_GET_DEVICE_LISTS_STOP, 'result': true, 'data': res }); }, fail: function (res) { mDeviceEvent.notifyDeviceMsgEvent({ 'type': mDeviceEvent.XBLUFI_TYPE.TYPE_GET_DEVICE_LISTS_STOP, 'result': false, 'data': res }); } }); } }); // 监听连接/断开蓝牙设备事件 (按要求,此函数未修改) mDeviceEvent.listenConnectBle(true, function (options) { //console.log("我要连接?", (options.isStart)) if (options.isStart) wx.createBLEConnection({ deviceId: options.deviceId, success: function (res) { wx.setBLEMTU({ deviceId: options.deviceId, mtu: 128 }) _self.data.deviceId = options.deviceId mDeviceEvent.notifyDeviceMsgEvent({ 'type': mDeviceEvent.XBLUFI_TYPE.TYPE_CONNECTED, 'result': true, 'data': { deviceId: options.deviceId, name: options.name }, }); }, fail: function (res) { _self.data.deviceId = null mDeviceEvent.notifyDeviceMsgEvent({ 'type': mDeviceEvent.XBLUFI_TYPE.TYPE_CONNECTED, 'result': false, 'data': res, }); } }); else wx.closeBLEConnection({ deviceId: options.deviceId, success: function (res) { console.log('断开成功') _self.data.deviceId = null mDeviceEvent.notifyDeviceMsgEvent({ 'type': mDeviceEvent.XBLUFI_TYPE.TYPE_CLOSE_CONNECTED, 'result': true, 'data': { deviceId: options.deviceId, name: options.name } }); }, fail: function (res) { _self.data.deviceId = null mDeviceEvent.notifyDeviceMsgEvent({ 'type': mDeviceEvent.XBLUFI_TYPE.TYPE_CLOSE_CONNECTED, 'result': false, 'data': res, }); } }) }); // 监听初始化ESP32(BluFi流程)事件 mDeviceEvent.listenInitBleEsp32(true, function (options) { // 重置状态 sequenceControl = 0; _self.data.result = []; _self.data.deviceId = options.deviceId; wx.getBLEDeviceServices({ deviceId: options.deviceId, success: function (res) { const service = res.services.find(s => s.uuid === _self.data.service_uuid); if (!service) { throw new Error("Blufi service not found"); } const serviceId = service.uuid; wx.getBLEDeviceCharacteristics({ deviceId: options.deviceId, serviceId: serviceId, success: function (charRes) { const writeChar = charRes.characteristics.find(c => c.uuid === _self.data.characteristic_write_uuid); const readChar = charRes.characteristics.find(c => c.uuid === _self.data.characteristic_read_uuid); if (!writeChar || !readChar) { throw new Error("Blufi characteristics not found"); } _self.data.serviceId = serviceId; _self.data.uuid = writeChar.uuid; wx.notifyBLECharacteristicValueChange({ state: true, deviceId: options.deviceId, serviceId: serviceId, characteristicId: readChar.uuid, success: function (notifyRes) { // 启用通知成功,开始安全协商 client = util.blueDH(util.DH_P, util.DH_G, crypto); const kBytes = util.uint8ArrayToArray(client.getPublicKey()); const pBytes = util.hexByInt(util.DH_P); const gBytes = util.hexByInt(util.DH_G); const pgkLength = pBytes.length + gBytes.length + kBytes.length + 6; const data = [ util.NEG_SET_SEC_TOTAL_LEN, (pgkLength >> 8) & 0xff, pgkLength & 0xff ]; const frameControl = util.getFrameCTRLValue(false, false, util.DIRECTION_OUTPUT, false, false); const value = util.writeData(util.PACKAGE_VALUE, util.SUBTYPE_NEG, frameControl, sequenceControl, data.length, data); const typedArray = new Uint8Array(value); wx.writeBLECharacteristicValue({ deviceId: options.deviceId, serviceId: serviceId, characteristicId: writeChar.uuid, value: typedArray.buffer, success: function (writeRes) { getSecret(options.deviceId, serviceId, writeChar.uuid, client, kBytes, pBytes, gBytes, null); }, fail: (err) => mDeviceEvent.notifyDeviceMsgEvent({ 'type': mDeviceEvent.XBLUFI_TYPE.TYPE_INIT_ESP32_RESULT, 'result': false, 'data': err }) }); // 监听从设备返回的数据 wx.onBLECharacteristicValueChange(function (changeRes) { const list2 = (util.ab2hex(changeRes.value)); if (list2.length < 4) { return; } const val = parseInt(list2[0], 16); const type = val & 3; const subType = val >> 2; // 使用优化后的 payload 解析函数 const fragNum = util.hexToBinArray(list2[1]); const payload = parsePayload(fragNum, list2, _self.data.md5Key); _self.data.result = _self.data.result.concat(payload); if (_self.data.flagEnd) { const resultData = [..._self.data.result]; _self.data.result = []; // 清空缓存 if (type == 1) { // Type: Data console.log("Receive data, subType: ", subType); switch (subType) { case util.SUBTYPE_NEGOTIATION_NEG: // 安全协商响应 const arr = util.hexByInt(resultData.join("")); const clientSecret = client.computeSecret(new Uint8Array(arr)); _self.data.md5Key = md5.array(clientSecret); mDeviceEvent.notifyDeviceMsgEvent({ 'type': mDeviceEvent.XBLUFI_TYPE.TYPE_INIT_ESP32_RESULT, 'result': true, 'data': { deviceId: options.deviceId, serviceId, characteristicId: writeChar.uuid } }); break; case 15: // Wi-Fi连接状态报告 const stationStatus = String.fromCharCode(...resultData.map(hex => parseInt(hex, 16))); mDeviceEvent.notifyDeviceMsgEvent({ 'type': mDeviceEvent.XBLUFI_TYPE.TYPE_CONNECT_ROUTER_RESULT, 'result': resultData.length > 3, 'data': { 'progress': 100, 'status': stationStatus } }); break; case 17: // 附近Wi-Fi列表 getList(resultData); break; case 19: // 自定义数据 const customDataStr = String.fromCharCode(...resultData.map(hex => parseInt(hex, 16))); mDeviceEvent.notifyDeviceMsgEvent({ 'type': mDeviceEvent.XBLUFI_TYPE.TYPE_RECIEVE_CUSTON_DATA, 'result': true, 'data': customDataStr }); break; default: console.log("Unhandled data subType:", subType); break; } } else { console.error("Received an error type frame:", type); } } }); }, fail: (err) => mDeviceEvent.notifyDeviceMsgEvent({ 'type': mDeviceEvent.XBLUFI_TYPE.TYPE_INIT_ESP32_RESULT, 'result': false, 'data': err }) }); }, fail: (err) => mDeviceEvent.notifyDeviceMsgEvent({ 'type': mDeviceEvent.XBLUFI_TYPE.TYPE_INIT_ESP32_RESULT, 'result': false, 'data': err }) }); }, fail: (err) => mDeviceEvent.notifyDeviceMsgEvent({ 'type': mDeviceEvent.XBLUFI_TYPE.TYPE_INIT_ESP32_RESULT, 'result': false, 'data': err }) }); }); // 监听发送SSID和密码事件 mDeviceEvent.listenSendRouterSsidAndPassword(true, function (options) { _self.data.password = options.password; _self.data.ssid = options.ssid; writeDeviceRouterInfoStart(); }); // 监听发送自定义数据事件 mDeviceEvent.listenSendCustomData(true, function (options) { _self.data.customData = options.customData; writeCutomsData(); }); // 监听发送获取附近Wi-Fi指令事件 mDeviceEvent.listenSendGetNearRouterSsid(true, function(options) { writeGetNearRouterSsid(); }); } /** * [优化] 处理Wi-Fi列表数据 * 使用循环和索引替代递归和splice,性能更高。 * @param {number[]} arr - 包含所有Wi-Fi信息的字节数组 */ function getList(arr) { let currentIndex = 0; while (currentIndex < arr.length) { const len = parseInt(arr[currentIndex], 16); if (len <= 0) { // 长度异常,跳出循环 break; } // 检查数据是否完整 if (currentIndex + 1 + len > arr.length) { console.error("Incomplete Wi-Fi data frame."); break; } const rssi = parseInt(arr[currentIndex + 1], 16); const nameBytes = arr.slice(currentIndex + 2, currentIndex + 1 + len); const name = String.fromCharCode(...nameBytes.map(hex => parseInt(hex, 16))); mDeviceEvent.notifyDeviceMsgEvent({ 'type': mDeviceEvent.XBLUFI_TYPE.TYPE_CONNECT_NEAR_ROUTER_LISTS, 'result': true, 'data': { "rssi": rssi, "SSID": decodeURIComponent(escape(name)) } // 保持和原逻辑一致的解码方式 }); // 移动索引到下一个Wi-Fi信息的开头 currentIndex += (1 + len); } } /****************************** 对外导出 ***************************************/ module.exports = { init: init, };