commit 010d7e29b274efed421602af5a83cfd47a17e35f Author: facaige888 <136196984@qq.com> Date: Mon May 19 13:35:52 2025 +0800 Initial Commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..14ea590 --- /dev/null +++ b/.gitignore @@ -0,0 +1,14 @@ +# Windows +[Dd]esktop.ini +Thumbs.db +$RECYCLE.BIN/ + +# macOS +.DS_Store +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes + +# Node.js +node_modules/ diff --git a/app.js b/app.js new file mode 100644 index 0000000..0894cc5 --- /dev/null +++ b/app.js @@ -0,0 +1,28 @@ +// app.js +App({ + onLaunch() { + // 展示本地存储能力 + const logs = wx.getStorageSync('logs') || [] + logs.unshift(Date.now()) + wx.setStorageSync('logs', logs) + + // 登录 + wx.login({ + success: res => { + // 发送 res.code 到后台换取 openId, sessionKey, unionId + } + }) + }, + globalData: { + userInfo: null, + deviceId:null, + writeUuid:'0000ffe2-0000-1000-8000-00805f9b34fb', + notifiUuid:'0000ffe1-0000-1000-8000-00805f9b34fb', + serviceUuid:'0000ffe0-0000-1000-8000-00805f9b34fb', + + bleDeviceId: "", // 设备ID + bleServiceId: "", // 服务UUID + notifyCharId: "", // 监听特征值UUID + writeCharId: "" // 写入特征值UUID + } +}) diff --git a/app.json b/app.json new file mode 100644 index 0000000..d3920a1 --- /dev/null +++ b/app.json @@ -0,0 +1,29 @@ +{ + "pages": [ + "pages/index/index", + "pages/detail/detail", + "pages/datalist/brand", + "pages/newdetail/newdetail", + "pages/playvideo/video", + "pages/service/service", + "pages/newzk/newlist" + ], + "permission": { + "scope.userLocation": { + "desc": "需要获取位置信息以搜索蓝牙设备" + }, + "scope.bluetooth": { + "desc": "需要蓝牙权限以操作设备" + } + }, + "requiredBackgroundModes": ["bluetooth"], + "window": { + "navigationBarTextStyle": "black", + "navigationBarTitleText": "Weixin", + "navigationBarBackgroundColor": "#ffffff" + }, + "style": "v2", + "componentFramework": "glass-easel", + "sitemapLocation": "sitemap.json", + "lazyCodeLoading": "requiredComponents" +} diff --git a/app.wxss b/app.wxss new file mode 100644 index 0000000..06c6fc9 --- /dev/null +++ b/app.wxss @@ -0,0 +1,10 @@ +/**app.wxss**/ +.container { + height: 100%; + display: flex; + flex-direction: column; + align-items: center; + justify-content: space-between; + padding: 200rpx 0; + box-sizing: border-box; +} diff --git a/pages/datalist/brand.js b/pages/datalist/brand.js new file mode 100644 index 0000000..adc2d99 --- /dev/null +++ b/pages/datalist/brand.js @@ -0,0 +1,74 @@ +Page({ + data: { + list:[], + children:[] + }, + toggleExpand(e) { + const id = e.currentTarget.dataset.id; + const list = this.data.list.map(item => { + if (item.id === id) { + return { ...item, haveChildren: !item.haveChildren }; + } else { + return { ...item, haveChildren: false }; + } + }); + this.setData({ list }); + if(!e.currentTarget.dataset.haveChildren){ + wx.navigateTo({ + url: '/pages/detail/detail?name='+id // 支持参数传递 + }) + } + }, + // 在生命周期函数中加载数据 +onLoad(options) { + wx.showToast({ + title: `点击了子项 ${options.name}`, + icon: 'none' + }); + const s = options.name; + wx.request({ + url: 'https://keytest.aik518.com/prod-api/work_key/obd/key/type_blue?blueName='+s, + method: 'GET', // 请求方法 + success: res => { + this.setData({ + list:res.data.data + // list: res.data.map(item => ({ + // ...item, + // isExpanded: false, + // bgColor: '#fff' + // })) + }) + } + }) +}, +refresh: function(e) { + + wx.navigateTo({ + url: '/pages/detail/detail?id=1&name=example' + }); +}, +// 点击事件处理函数 +handleItemClick: function (e) { + // const index = e.currentTarget.dataset.index; // 主列表索引 + // const subIndex = e.currentTarget.dataset.subIndex; // 子列表索引 + // const subItem = this.data.list[index].sublist[subIndex]; + // wx.showToast({ title: `点击了 ${subItem.name}`, icon: 'none' }); + const childIndex = e.currentTarget.dataset.childIndex; + // const {parentIndex, childIndex} = e.currentTarget.dataset + // const childData = this.data.list[parentIndex].children[childIndex] + // const childId = e.currentTarget.dataset.child; + if(childIndex.haveChildren){ + this.setData({list:childIndex.children}); + }else{ + wx.navigateTo({ + url: '/pages/detail/detail?name='+childIndex.id // 支持参数传递 + }) + } + wx.showToast({ + title: `点击了子项 ${childIndex.id}`, + icon: 'none' + }); +}, + + +}) \ No newline at end of file diff --git a/pages/datalist/brand.json b/pages/datalist/brand.json new file mode 100644 index 0000000..a1328df --- /dev/null +++ b/pages/datalist/brand.json @@ -0,0 +1,6 @@ +{ + "usingComponents": { + + }, + "navigationBarTitleText": "OBD分类" +} diff --git a/pages/datalist/brand.wxml b/pages/datalist/brand.wxml new file mode 100644 index 0000000..6f1c09c --- /dev/null +++ b/pages/datalist/brand.wxml @@ -0,0 +1,27 @@ + + + {{item.name}}{{item.haveChildren ? '▼' : '▶'}} + + + {{child.name}} + + + + \ No newline at end of file diff --git a/pages/datalist/brand.wxss b/pages/datalist/brand.wxss new file mode 100644 index 0000000..61919d1 --- /dev/null +++ b/pages/datalist/brand.wxss @@ -0,0 +1,13 @@ +.list-item { + padding: 10px; + border-bottom: 1px solid #ccc; +} + +.sublist { + margin-left: 20px; +} + +.sublist-item { + padding: 5px; + border-bottom: 1px solid #ddd; +} \ No newline at end of file diff --git a/pages/detail/detail.js b/pages/detail/detail.js new file mode 100644 index 0000000..61ef3b6 --- /dev/null +++ b/pages/detail/detail.js @@ -0,0 +1,416 @@ +// import { writeBLECharacteristicValue, reverseHexString, intToHexString } from '../../utils/bleUtil.js' + +Page({ + data: { + // array: [{ + // message: 'foo', + // }, { + // message: 'bar' + // }] + list:[], + bean:{} + }, + updateList: function() { + const newArray = this.data.array.concat({ message: 'baz' }); + this.setData({ + array: newArray + }); + }, + // sendOrder:function(e){ + // const bean = + // }, + onLoad(options) { + wx.showToast({ + title: `点击了子项 ${options.name}`, + icon: 'none' + }); + const s = options.name; + wx.request({ + url: 'https://keytest.aik518.com/prod-api/work_key/obd/key/type/search', + method: 'POST', // 请求方法 + data: JSON.stringify({ // 手动序列化对象为 JSON 字符串 + typeId: s, + language: 'zh' + }), + header: { + 'Content-Type': 'application/json' // 关键标识‌:ml-citation{ref="1,4" data="citationList"} + }, + success: res => { + console.log(res); + this.setData({ + // list:res.data + list:res.data.records[0].commandMap + + // bean:res.data + + // list: res.data.map(item => ({ + // ...item, + // isExpanded: false, + // bgColor: '#fff' + // })) + }) + + } + }); + this.initBluetooth(); + + },initBluetooth:function () { + wx.getConnectedBluetoothDevices({ + services: [getApp().globalData.serviceUuid], + success: (res) => { + if (res.devices.length === 0) { + // 设备已断开,需重新连接 + console.error("重连失败:", 1) + this.reconnectDevice(); + } else { + console.error("重连失败:", 2) + // 直接复用全局参数,但需重新获取特征值(iOS限制) + this.getCharacteristics(); + } + } + }); + },reconnectDevice:function(){ + wx.createBLEConnection({ + deviceId: getApp().globalData.deviceId, + success: () => this.getCharacteristics(), + fail: (err) => console.error("重连失败:", err) + }); + },getCharacteristics:function(){ + console.error("重连失败:", getApp().globalData.deviceId) + console.error("重连失败:", getApp().globalData.serviceUuid) + // wx.getBLEDeviceCharacteristics({ + // deviceId: getApp().globalData.deviceId, + // serviceId: getApp().globalData.serviceUuid, + // success: (res) => { + // const notifyChar = res.characteristics.find(c => c.properties.notify); + // const writeChar = res.characteristics.find(c => c.properties.write); + // if (notifyChar && writeChar) { + // // 更新全局特征值(防止缓存失效) + // getApp().globalData.notifiUuid = notifyChar.uuid; + // getApp().globalData.serviceUuid = writeChar.uuid; + // this.enableNotify(); + // } + // }, + // fail: (err) => console.error("获取特征值失败:", err) + // }); + this.enableNotify(); + },enableNotify:function (){ + wx.notifyBLECharacteristicValueChange({ + deviceId: getApp().globalData.deviceId, + serviceId: getApp().globalData.serviceUuid, + characteristicId: getApp().globalData.notifiUuid, + state: true, + type: 'notification', // iOS 必填 + success: () => { + console.log("B页面通知已开启"); + this.listenData(); + }, + fail: (err) => console.error("B页面开启通知失败:", err) + }); + },checkSum:function (hexString) { + // 移除空格并转换为字节数组 + const hexArray = hexString.replace(/\s+/g, '').match(/.{2}/g); + if (!hexArray) return 0; + + // 将HEX字节转换为十进制并累加 + let sum = 0; + hexArray.forEach(hex => { + sum += parseInt(hex, 16); + }); + + // 取低8位 + let yu = sum%256; + let lastHex = yu.toString(16).padStart(2, '0'); + return lastHex; + }, + buildHex:function (params) { + const head = "A3"; + const realData = params; + const rl = realData.length; + let l = rl/2+4; + const ll = l.toString(16); + const lll = ll.length; + let lastLenth = ""; + if(lll==1){ + lastLenth = "000"+ll; + }else if(lll==2){ + lastLenth= "00"+ll; + }else if(lll==3){ + lastLenth= "0"+ll; + }else if(lll>4){ + lastLenth = ll.substr(lll-4,lll); + } + const frameLenth = ""; //2字节 + let crc = "";//1 byte + let lldata = head+lastLenth+realData; + crc = this.checkSum(lldata); + const lastHex = head+lastLenth+realData+crc; + return lastHex; + },bytesToHex:function (bytes) { + let strHex = '' + for (let i = 0; i < bytes.length; i++) { + strHex = strHex + bytes[i].toString(16).padStart(2, '0').toUpperCase() + } + return strHex; + },solveHex:function (params) { + let hex = params.toString().toUpperCase; + console.log("hex====>"+hex) + if(hex.startsWith("A3")){ + let firstlenth = hex.substr(2,6); + let ff = parseInt(firstlenth,16); + if(ff/2 ==hex.length){ + let realOrder = hex.substr(6,8); + switch(realOrder){ + case "A0": + break; + case "A1": + break; + case "A2": + wx.showModal({ + title: '提示', + content: '是否清楚故障码?', + success (res) { + if (res.confirm) { + console.log('用户点击确定') + } else if (res.cancel) { + console.log('用户点击取消') + } + } + }) + break; + case "A3": + break; + case "A4": + break; + case "A5": + break; + + } + }else{ + let firstOrder + } + } + }, + listenData:function () { + wx.onBLECharacteristicValueChange((res) => { + const value = new Uint8Array(res.value); + const hex = this.bytesToHex(value); + console.log("B页面收到数据:", value +"==="+hex); + // this.solveHex(hex); + //A3001246554E4354494F4E5F535441525408 A3000A A2F00000 15 00 54 + //A3001246554E4354494F4E5F535441525408 A3000AA2F00000150054 + // let hex = params.toString().toUpperCase; + //A300094552524F5236 A300064F4B43 A300064F4B43 + //A3000B 554E4B4E4F574E DE + //A3000A A0100000070064 A3000AA0100000070064 + //A300974C4F470000C1A50100000745080210C000000000000000C1B40200000765080250C0FFFFFFFFFF0000C1E90100000745080210C000000000000000C1F00200000765080250C0FFFFFFFFFF0000C25401000007450802270100000000000000C25F020000076508066701253FFCEFFF0000C2C3010000074508062702F9 + //A300734C4F470000C350010000074508023E0100000000000000C350020000076508017EFFFFFFFFFFFF0000C3DE01000007450802212100000000000000C3F9020000076508100D6121020F8EFD0000C3F901000007450830000000000000000000C3FA0200000765082140EFAEED495C418F + + //A3001346554E4354494F4E5F46494E4953483C + console.log("hex====>"+hex) + if(hex.startsWith("A3")){ + let firstlenth = hex.substring(2,6); //1246 + let ff = parseInt(firstlenth,16); + + console.log("ff====>"+ff+"hex.length===>"+hex.length+"firstlenth===>"+firstlenth); + if(ff*2 ==hex.length){ + let realOrder = hex.substring(6,8); + this.showOrder(realOrder); + }else{ + let firstOrder = hex.substring(ff*2,hex.length); + + console.log("firstOrder====>"+firstOrder); + if(firstOrder.startsWith("A3")){ + let xx = firstOrder.substring(2,6); + let yy = parseInt(xx,16); + + console.log("xx====>"+xx+"yy"+yy); + if(yy*2 ==firstOrder.length){ + let realOrder = firstOrder.substring(6,8); + this.showOrder(realOrder); + }else{ + let secondOrder = hex.substring(yy*2,firstOrder.length); + if(secondOrder.startsWith("A3")){ + let zz = secondOrder.substring(2,6); + let rr = parseInt(zz,16); + if(rr*2 == secondOrder.length){ + let realSecondOrder = secondOrder.substring(6,8); + this.showOrder(realSecondOrder); + } + } + } + }else{ + + } + } + } + // 自定义数据处理逻辑... + //163, 0, 18, 70, 85, 78, 67, 84, 73, 79, 78, 95, 83, 84, 65, 82, 84, 8, 163, 0, 10, 162, 240, 0, 0, 21, 0, 84 + //A30012 46554E4354494F4E5F5354415254 08 A3000AA2F00000150054 + }); + }, + showOrder(realOrder){ + var that = this; + let order = ""; + if(realOrder=="a0"){ + wx.showModal({ + title: '提示', + content: '正在读取,请稍后!', + showCancel:false, + success (res) { + if (res.confirm) { + + } + } + }) + }else if(realOrder=="A1"){ + wx.showModal({ + title: '提示', + content: '无故障码!', + showCancel:false, + success (res) { + if (res.confirm) { + order = "ccc1"; + that.sendCommonOrder(order); + console.log('用户点击确定') + } + } + }) + }else if(realOrder=="A2"){ + wx.showModal({ + title: '提示', + content: '是否清楚故障码?', + success (res) { + if (res.confirm) { + order = "ccc2aa"; + that.sendCommonOrder(order); + console.log('用户点击确定') + } else if (res.cancel) { + order = "ccc255"; + that.sendCommonOrder(order); + console.log('用户点击取消') + } + } + }) + }else if(realOrder =="A3"){ + wx.showModal({ + title: '匹配遥控', + content: '请输入要匹配的遥控数量(范围1-5)', + editable:true, + success (res) { + if (res.confirm) { + order = "ccc2aa"; + that.sendCommonOrder(order); + console.log('用户点击确定') + } else if (res.cancel) { + order = "ccc255"; + that.sendCommonOrder(order); + console.log('用户点击取消') + } + } + }) + }else if(realOrder =="A4"){ + + }else if(realOrder =="A5"){ + + } + + + },sendCommonOrder:function (hexString){ + const hex = this.buildHex(hexString); + console.log("发送指令:", hex); + var buffer = new ArrayBuffer(1); + var dataView = new DataView(buffer); + dataView.setUint8(0, 0x01); // 示例数据 + const bbbb = hexStringToArrayBuffer(hex); + wx.writeBLECharacteristicValue({ + // AA60000005 + deviceId: getApp().globalData.deviceId, + serviceId: getApp().globalData.serviceUuid, + characteristicId: getApp().globalData.writeUuid, + value: bbbb, + success: function(res) { + console.log('发送成功'); + }, + fail: function(err) { + console.error('发送失败', err); + } + }); + // HEX转ArrayBuffer工具函数 + function hexStringToArrayBuffer(hex) { + const bytes = new Uint8Array(hex.match(/[\da-f]{2}/gi).map(h => parseInt(h, 16))); + return bytes.buffer; +} + },sendBlueOrder:function(e){ + const oder = "aa"+e.currentTarget.dataset.value; + let oo = "aa60000001"; + this.sendCommonOrder(oo); + // const hex = this.buildHex(oder); + + // console.log("发送指令:", hex); + // var buffer = new ArrayBuffer(1); + // var dataView = new DataView(buffer); + // dataView.setUint8(0, 0x01); // 示例数据 + + + // var app = getApp(); + // const bbbb = hexStringToArrayBuffer(hex); + // wx.writeBLECharacteristicValue({ + // // AA60000005 + // deviceId: getApp().globalData.deviceId, + // serviceId: getApp().globalData.serviceUuid, + // characteristicId: getApp().globalData.writeUuid, + // value: bbbb, + // success: function(res) { + // console.log('发送成功'); + // }, + // fail: function(err) { + // console.error('发送失败', err); + // } + // }); + // // HEX转ArrayBuffer工具函数 + // function hexStringToArrayBuffer(hex) { + // const bytes = new Uint8Array(hex.match(/[\da-f]{2}/gi).map(h => parseInt(h, 16))); + // return bytes.buffer; +// } + }, + startNotifi:function(){ + + + + + // 启用通知 + + var app = getApp(); +wx.notifyBLECharacteristicValueChange({ + deviceId: app.globalData.deviceId, + serviceId: '0000ffe0-0000-1000-8000-00805f9b34fb', + characteristicId: '0000ffe1-0000-1000-8000-00805f9b34fb', + state: true, + success: function(res) { + console.log('启用通知成功'); + } +}); + +// 监听数据 +wx.onBLECharacteristicValueChange(function(res) { + console.log('收到数据', res.value); +}); + }, + getDevice:function(){ + wx.getBLEDeviceServices({ + deviceId: app.globalData.deviceId, + success: function(res) { + res.services.forEach(function(service) { + wx.getBLEDeviceCharacteristics({ + deviceId: app.globalData.deviceId, + serviceId: service.uuid, + success: function(res) { + console.log('特征值列表', res.characteristics); + } + }); + }); + } + }); + } +}) \ No newline at end of file diff --git a/pages/detail/detail.json b/pages/detail/detail.json new file mode 100644 index 0000000..374ad19 --- /dev/null +++ b/pages/detail/detail.json @@ -0,0 +1,6 @@ +{ + "usingComponents": { + + }, + "navigationBarTitleText": "详情" +} diff --git a/pages/detail/detail.wxml b/pages/detail/detail.wxml new file mode 100644 index 0000000..309f276 --- /dev/null +++ b/pages/detail/detail.wxml @@ -0,0 +1,3 @@ + + {{item.key}}{{item.value}} + \ No newline at end of file diff --git a/pages/detail/detail.wxss b/pages/detail/detail.wxss new file mode 100644 index 0000000..c595663 --- /dev/null +++ b/pages/detail/detail.wxss @@ -0,0 +1,4 @@ +.list-item { + padding: 10px; + border-bottom: 1px solid #ccc; +} \ No newline at end of file diff --git a/pages/index/index.js b/pages/index/index.js new file mode 100644 index 0000000..fb84176 --- /dev/null +++ b/pages/index/index.js @@ -0,0 +1,172 @@ +Page({ + data: { + deviceList: [], + isSearching: false + }, + + // 开始搜索 请给出一个微信小程序可展开列表并且展开后是一个列表,并且宽度占满屏幕,展开后父列表改变背景颜色,缩起其它已展开的列表的示例代码 + startSearch() { + + + if (this.data.isSearching) return; + + wx.openBluetoothAdapter({ + mode: 'central', // ios必须要带这个参数 + success: () => { + this.setData({ isSearching: true }); + this.startDiscovery(); + }, + fail: (err) => { + wx.showToast({ title: '请开启手机蓝牙', icon: 'none' }); + console.error('蓝牙初始化失败:', err); + } + }); + + // wx.navigateTo({ + // // url: '/pages/datalist/brand?name='+'FORD_OBD' // 支持参数传递 + // url: '/pages/newzk/newlist?name='+"FORD_OBD" // 支持参数传递 + // }) + // wx.showToast({ title: '连接成功'+deviceName, icon: 'success' }); + + }, + + // 启动设备发现 + startDiscovery() { + wx.startBluetoothDevicesDiscovery({ + allowDuplicatesKey: false, + success: () => { + this.listenDevices(); + setTimeout(() => this.stopDiscovery(), 7000); // 10秒后停止搜索 + }, + fail: (err) => { + console.error('搜索启动失败:', err); + this.setData({ isSearching: false }); + } + }); + }, + + // 监听发现设备 + listenDevices() { + wx.onBluetoothDeviceFound((res) => { + const newDevices = res.devices.filter(device => + !this.data.deviceList.some(d => d.deviceId === device.deviceId), + ); + + // const blename = newDevices.blename + // if(blename.includes('FORD')){ + const filteredDevices = newDevices.filter(device => + device.name.includes("OBD"), + ); + // this.setData({ devices: filteredDevices }); + // } + this.setData({ + deviceList: [...this.data.deviceList, ...filteredDevices] + }); + + }); + }, + + // 停止搜索 + stopDiscovery() { + wx.stopBluetoothDevicesDiscovery({ + success: () => { + this.setData({ isSearching: false }); + // wx.showToast({ title: '搜索完成', icon: 'success' }); + } + }); + }, + + // 设备连接(示例框架) + connectDevice(e) { + + const deviceId = e.currentTarget.dataset.deviceid; + const deviceName = e.currentTarget.dataset.blename; + const app = getApp(); + + + wx.createBLEConnection({ + deviceId, + success: () => { + getApp().globalData.deviceId = deviceId; + + wx.navigateTo({ + // url: '/pages/datalist/brand?name='+deviceName // 支持参数传递 + url: '/pages/newzk/newlist?name='+deviceName // 支持参数传递 + }) + + this.discoverServices(deviceId); + wx.showToast({ title: '连接成功'+deviceName+deviceId, icon: 'success' }); + } + + + }); + + wx.onBLEConnectionStateChange((res) => { + if (!res.connected) { + console.log("设备已断开,尝试重连..."); + this.reconnectDevice(); // 触发重连逻辑 + } + }); + }, + discoverServices(deviceId) { + wx.getBLEDeviceServices({ + deviceId: deviceId, + success: (res) => { + // 假设目标服务UUID为 FEE0(需替换为实际值) + const targetServiceId = res.services.find(s => s.uuid.toLowerCase() === '0000ffe0-0000-1000-8000-00805f9b34fb').uuid; + let upSUuid = targetServiceId.toUpperCase(); + getApp().globalData.serviceUuid = upSUuid; + + console.error('可写特征值1:', upSUuid); + console.error('可写特征值2:', getApp().globalData.serviceUuid); + this.getCharacteristics(deviceId, targetServiceId); + }, + fail: (err) => console.error('发现服务失败', err) + }); + }, + getCharacteristics(deviceId, serviceId) { + wx.getBLEDeviceCharacteristics({ + deviceId: deviceId, + serviceId: serviceId, + success: (res) => { + const characteristics = res.characteristics; + // let writeUuid = '', notifiUuid = ''; + + characteristics.forEach(char => { + if (char.properties.writeNoResponse){ + let uuidNo = char.uuid; + let upUuid = uuidNo.toUpperCase(); + getApp().globalData.writeUuid = upUuid; // 可写特征 + } + // if(char.properties.notify){ + // let uuidNo = char.uuid; + // let upUuid = uuidNo.toUpperCase(); + // getApp().globalData.notifiUuid = upUuid; + // } + if(char.uuid == '0000ffe3-0000-1000-8000-00805f9b34fb'){ + let uuidNo = char.uuid; + let upUuid = uuidNo.toUpperCase(); + getApp().globalData.notifiUuid = upUuid; + } + + // if (char.properties.) getApp().globalData.notifyUUID = char.uuid; // 通知特征 + }); + + // console.log('可写特征值:', writeUuid); + // console.log('通知特征值:', notifiUuid); + + // 启用通知(必须) + if (notifyUUID) { + wx.notifyBLECharacteristicValueChange({ + deviceId: deviceId, + serviceId: serviceId, + characteristicId: notifyUUID, + state: true, + success: () => console.log('通知已启用') + }); + } + }, + fail: (err) => console.error('获取特征值失败', err) + }); + } +}) diff --git a/pages/index/index.json b/pages/index/index.json new file mode 100644 index 0000000..af52a92 --- /dev/null +++ b/pages/index/index.json @@ -0,0 +1,5 @@ +{ + "usingComponents": { + }, + "navigationBarTitleText": "CAR匹配" +} \ No newline at end of file diff --git a/pages/index/index.wxml b/pages/index/index.wxml new file mode 100644 index 0000000..a88607c --- /dev/null +++ b/pages/index/index.wxml @@ -0,0 +1,41 @@ + + + + + 请确保OBD设备已经连接汽车OBD接口,并未与其它手机连接 + + + + + + + {{item.name || '未知设备'}} + 信号强度: {{item.RSSI}}dBm + MAC: {{item.deviceId}} + + + + + diff --git a/pages/index/index.wxss b/pages/index/index.wxss new file mode 100644 index 0000000..95592b4 --- /dev/null +++ b/pages/index/index.wxss @@ -0,0 +1,33 @@ +.container1 { + padding: 20rpx; + +} +.s-button{ + display: block; + width: 100%; +} +.device-notice{ + display: block; + font-size: 18rpx; + display: flex; + align-items: center; /* 垂直居中 */ + justify-content: center; /* 水平居中 */ + color: #666; + margin-top: 5rpx; +} + +.device-item { + padding: 20rpx; + margin: 20rpx 0; + border-bottom: 1rpx solid rgb(48, 29, 219); +} + +.device-name { + display: block; + font-size: 32rpx; +} + +.device-rssi { + color: #666; + font-size: 28rpx; +} diff --git a/pages/logs/logs.js b/pages/logs/logs.js new file mode 100644 index 0000000..85f6aac --- /dev/null +++ b/pages/logs/logs.js @@ -0,0 +1,18 @@ +// logs.js +const util = require('../../utils/util.js') + +Page({ + data: { + logs: [] + }, + onLoad() { + this.setData({ + logs: (wx.getStorageSync('logs') || []).map(log => { + return { + date: util.formatTime(new Date(log)), + timeStamp: log + } + }) + }) + } +}) diff --git a/pages/logs/logs.json b/pages/logs/logs.json new file mode 100644 index 0000000..b55b5a2 --- /dev/null +++ b/pages/logs/logs.json @@ -0,0 +1,4 @@ +{ + "usingComponents": { + } +} \ No newline at end of file diff --git a/pages/logs/logs.wxml b/pages/logs/logs.wxml new file mode 100644 index 0000000..85cf1bf --- /dev/null +++ b/pages/logs/logs.wxml @@ -0,0 +1,6 @@ + + + + {{index + 1}}. {{log.date}} + + diff --git a/pages/logs/logs.wxss b/pages/logs/logs.wxss new file mode 100644 index 0000000..33f9d9e --- /dev/null +++ b/pages/logs/logs.wxss @@ -0,0 +1,16 @@ +page { + height: 100vh; + display: flex; + flex-direction: column; +} +.scrollarea { + flex: 1; + overflow-y: hidden; +} +.log-item { + margin-top: 20rpx; + text-align: center; +} +.log-item:last-child { + padding-bottom: env(safe-area-inset-bottom); +} diff --git a/pages/newdetail/newdetail.js b/pages/newdetail/newdetail.js new file mode 100644 index 0000000..93740c8 --- /dev/null +++ b/pages/newdetail/newdetail.js @@ -0,0 +1,554 @@ +import { +   onBLEConChange, +   addConnectionListener, +   handleBleData, +   writeBLECharacteristicValue, +   onBLECharacteristicValueChange, +   stringToHex, +   intToHexString + } from '../../utils/bleUtil.js' +Page({ + data: { + list:[], + obd_command:[], + commandMap:[], + support:"", + codeList:[], + videoUrl:"" + }, + // 在生命周期函数中加载数据 +onLoad(options) { + //   onBLECharacteristicValueChange((result) => { + //       this.handleResult(result) + //     }) +   + + wx.showToast({ + title: `点击了子项 ${options.name}`, + icon: 'none' + }); + // wx.showLoading({title:"等待页面初始化"}) + const s = options.name; + wx.request({ + url: 'https://keytest.aik518.com/prod-api/work_key/obd/zk_key/type/search', + method: 'POST', // 请求方法 + data: JSON.stringify({ // 手动序列化对象为 JSON 字符串 + typeId: s, + language: 'zh' + }), + header: { + 'Content-Type': 'application/json' // 关键标识‌:ml-citation{ref="1,4" data="citationList"} + }, + success: res => { + console.log(res); + this.setData({ + + obd_command:res.data.dict.obd_command, + commandMap:res.data.records[0].commandMap, + support:res.data.records[0].support, + videoUrl:res.data.records[0].videoUrl, + codeList:res.data.records[0].type.codeList + + + + }) + + } + }); + + this.initBluetooth(); +},getservicedata(s){ + wx.request({ + url: 'https://keytest.aik518.com/prod-api/work_key/obd/zk_key/calc_pin?bcmCode='+s, + method: 'GET', // 请求方法 + success: res => { + //{"code":1,"data":{"oldPin":"0862","newPin":"0746","flag":true},"dict":null,"i18nMsg":"请求成功","msg":"请求成功"} + // A3000AAA5501 0CC2FB 76 + let oldPin = res.data.data.oldPin; + let newPin = res.data.data.newPin; + console.log(res.data+"==="+oldPin+"==="+newPin); + wx.showModal({ + title: '密钥', + content: '老密钥:'+oldPin+" 新密钥:"+newPin, + complete: (res) => { + if (res.confirm) { + + } + } + }) + + } + }) +},initBluetooth:function () { + wx.getConnectedBluetoothDevices({ + services: [getApp().globalData.serviceUuid], + success: (res) => { + if (res.devices.length === 0) { + // 设备已断开,需重新连接 + console.error("重连失败:", 1) + this.reconnectDevice(); + } else { + console.error("重连失败:", 2) + // 直接复用全局参数,但需重新获取特征值(iOS限制) + this.getCharacteristics(); + } + } + }); +}, +reconnectDevice:function(){ + wx.createBLEConnection({ + deviceId: getApp().globalData.deviceId, + success: () => this.getCharacteristics(), + fail: (err) => console.error("重连失败:", err) + }); +}, +getCharacteristics:function(){ + console.error("成功==>设备id:", getApp().globalData.deviceId) + console.error("成功===>服务UUID:", getApp().globalData.serviceUuid) + this.enableNotify(); +}, +enableNotify:function (){ + wx.notifyBLECharacteristicValueChange({ + deviceId: getApp().globalData.deviceId, + serviceId: getApp().globalData.serviceUuid, + characteristicId: getApp().globalData.notifiUuid, + state: true, + type: 'notification', // iOS 必填 + success: () => { + console.log("打开通道成功====>"+getApp().globalData.notifiUuid); + wx.showToast({ + title: '初始化成功', + }) + wx.hideLoading(); + this.listenData(); + }, + // fail: (err) => console.error("B页面开启通知失败:", err) + fail:(err) => wx.showModal({ + title: '页面开启通知失败', + content: err, + complete: (res) => { + + + if (res.confirm) { + + } + } + }) + }); +},listenData:function () { + wx.onBLECharacteristicValueChange((res) => { + + const value = new Uint8Array(res.value); + let hex = this.bytesToHex(value); + console.log("当前页面收到数据:", value +"==="+hex); + // wx.showModal({ + // title: '页面收到数据', + // content: hex, + // complete: (res) => { + // if (res.confirm) { + + // } + // } + // }) + //470007650804 A3000AA1F000001B0059 + //023E0100000000000000C350020000076508C2 A3000B4C4F47017EFFFF0D A3002F4C4F47FFFFFFFF0000C738010000074508023E0100000000000000C738020000076508017EFFFFFFFFFFFF33 A3000AA11000000B0069 + console.log("hex====>"+hex) + //A3000AAA55010E19C195 A3000AAA55010E19C195 + if(hex.includes("AA5501")&&hex.includes("A300")){ + wx.hideLoading(); + let index = hex.lastIndexOf("AA5501"); + index = index+6; + let miyao = hex.substring(index,index+6); + console.log("密钥==>"+miyao); + let finishOrder = "A30006CCC136"; + this.sendCommonOrder(finishOrder,false); + this.getservicedata(miyao); + return; + } + + if(!hex.startsWith("A3")&&hex.includes("A300")){ + let index = hex.lastIndexOf("A300"); + console.log("index===>"+index); + hex = hex.substring(index,hex.length); + console.log("hex===>"+hex); + } + if(hex.startsWith("A3")){ + let firstlenth = hex.substring(2,6); //1246 + let ff = parseInt(firstlenth,16); + + console.log("ff====>"+ff+"hex.length===>"+hex.length+"firstlenth===>"+firstlenth); + if(ff*2 ==hex.length){ + let realOrder = hex.substring(6,8); + let content = hex.substring(8,16); + let inputType = ""; + let number = hex.substring(16,18); + let realCount = 0; + if(number !=="00"){ + let type = hex.substring(18,20); + let numberLenth = parseInt(number); + let indexLast = 20+numberLenth*2; + let count = hex.substring(20,indexLast); + if(type =="A1"){ + realCount = parseInt(count); + }else if(type =="A2"){ + realCount = parseInt(count,16); + }else if(type =="A3"){ + + } + + } + // let inputContent = ""; + // let noticeConetntLengh = hex.substring(16,18); + // let noticeContentType = ""; + if(realOrder=="A3"){ + //A3 00 0D A1 10 00 00 01 02 A1 10 11 27 + //A3000A A3 A2 F000002 608 + //A3000AA3 A1 F000003112 本田 输入框命令 + content = hex.substring(10,18); + inputType = hex.substring(8,10); + // noticeConetntLengh = hex.substring(18,20); + // inputContent = hex.substring(); + + } + + + this.showOrder(realOrder,content,inputType,realCount); + }else{ + let firstOrder = hex.substring(ff*2,hex.length); + + console.log("firstOrder====>"+firstOrder); + if(firstOrder.startsWith("A3")){ + let xx = firstOrder.substring(2,6); + let yy = parseInt(xx,16); + + console.log("xx====>"+xx+"yy"+yy); + if(yy*2 ==firstOrder.length){ + let realOrder = firstOrder.substring(6,8); + let content2 = firstOrder.substring(8,16); + let inputType = ""; + if(realOrder=="A3"){ + content2 = firstOrder.substring(10,18); + inputType = firstOrder.substring(8,10); + } + + let number = firstOrder.substring(16,18); + let realCount = 0; + if(number !=="00"){ + let type = firstOrder.substring(18,20); + let numberLenth = parseInt(number); + let indexLast = 20+numberLenth*2; + let count = firstOrder.substring(20,indexLast); + if(type =="A1"){ + realCount = parseInt(count); + }else if(type =="A2"){ + realCount = parseInt(count,16); + }else if(type =="A3"){ + + } + + } + + + this.showOrder(realOrder,content2,inputType,realCount); + }else{ + let secondOrder = hex.substring(yy*2,firstOrder.length); + if(secondOrder.startsWith("A3")){ + let zz = secondOrder.substring(2,6); + let rr = parseInt(zz,16); + if(rr*2 == secondOrder.length){ + let realSecondOrder = secondOrder.substring(6,8); + let content3 = secondOrder.substring(8,16); + let inputType = ""; + if(realSecondOrder=="A3"){ + content3 = secondOrder.substring(10,18); + inputType =secondOrder.substring(8,10); + } + + let number = secondOrder.substring(16,18); + let realCount = 0; + if(number !=="00"){ + let type = secondOrder.substring(18,20); + let numberLenth = parseInt(number); + let indexLast = 20+numberLenth*2; + let count = secondOrder.substring(20,indexLast); + if(type =="A1"){ + realCount = parseInt(count); + }else if(type =="A2"){ + realCount = parseInt(count,16); + }else if(type =="A3"){ + + } + + } + this.showOrder(realSecondOrder,content3,inputType,realCount); + } + } + } + }else{ + + } + } + } + }); +},checkSum:function (hexString) { + // 移除空格并转换为字节数组 + const hexArray = hexString.replace(/\s+/g, '').match(/.{2}/g); + if (!hexArray) return 0; + + // 将HEX字节转换为十进制并累加 + let sum = 0; + hexArray.forEach(hex => { + sum += parseInt(hex, 16); + }); + + // 取低8位 + let yu = sum%256; +let lastHex = yu.toString(16).padStart(2, '0'); + return lastHex; + }, + buildHex:function (params) { + const head = "A3"; + const realData = params; + const rl = realData.length; + let l = rl/2+4; + const ll = l.toString(16); + const lll = ll.length; + let lastLenth = ""; + if(lll==1){ + lastLenth = "000"+ll; + }else if(lll==2){ + lastLenth= "00"+ll; + }else if(lll==3){ + lastLenth= "0"+ll; + }else if(lll>4){ + lastLenth = ll.substr(lll-4,lll); + } + const frameLenth = ""; //2字节 + let crc = "";//1 byte + let lldata = head+lastLenth+realData; + crc = this.checkSum(lldata); + const lastHex = head+lastLenth+realData+crc; + return lastHex; + },bytesToHex:function (bytes) { + let strHex = '' + for (let i = 0; i < bytes.length; i++) { + strHex = strHex + bytes[i].toString(16).padStart(2, '0').toUpperCase() + } + return strHex; + },get10to16Hex:function(s){ + let hex = s.toString(16); + let hexS = hex.toUpperCase(); + if(hexS.length%2!=0){ + hexS = "0"+hexS; + } + return hexS; + }, +// 点击事件处理函数 +handleTextTap: function (e) { + const name = e.currentTarget.dataset.name; + wx.navigateTo({ + url: '/pages/playvideo/video?url='+encodeURIComponent(name) // 支持参数传递 + }) + +},addkey(e){ + +},service(e){ + +}, getContent(content){ + console.log("当前命令====>"+content); + let listdata = this.data.codeList; + for(let i=0;i"+listdata[i]); + return listdata[i].def; + } + } +}, +showOrder(realOrder,content,inputType,realCount){ + var that = this; + let order = ""; + let ccc = content.toUpperCase(); + let message = this.getContent(ccc); + + let realCountxxx = realCount; + if(realCount>0){ + message = message+realCountxxx; + } + + + console.log("返回showOrder命令====>"+realOrder+"===="+content+"===="+message+"==="+inputType); + if(realOrder=="A0"){ + wx.showLoading({ + title: message, + }) + // setTimeout(function () { + // wx.hideLoading() + // }, 2000) + }else if(realOrder=="A1"){ + wx.hideLoading(); + wx.showModal({ + title: '提示', + content: message, + showCancel:false, + success (res) { + if (res.confirm) { + order = "ccc1"; + that.sendCommonOrder(order,true); + console.log('用户点击确定') + } + } + }) + }else if(realOrder=="A2"){ + wx.hideLoading(); + wx.showModal({ + title: '提示', + content: message, + cancelText:"否", + confirmText:"是", + success (res) { + if (res.confirm) { + order = "ccc2aa"; + that.sendCommonOrder(order,true); + console.log('用户点击确定') + } else if (res.cancel) { + order = "ccc255"; + that.sendCommonOrder(order,true); + console.log('用户点击取消') + } + } + }) + }else if(realOrder =="A3"){ + wx.hideLoading(); + wx.showModal({ + title: '匹配遥控'+"\r\n"+message, + // content: message, + placeholderText:"请输入", + editable:true, + success (res) { + if (res.confirm) { + console.log("res.content===>"+res.content); + // let key = parseInt( res.content); + let key = res.content; + let lastHex = ""; + // // let keyHex = this.get10to16Hex(key); + // let keyHex = key.toString(16); + // keyHex = keyHex.toUpperCase(); + // if(keyHex.length%2!=0){ + // keyHex = "0"+keyHex; + // } + if(inputType=="A1"){ + let key1 = parseInt( key); + lastHex = that.get10to16Hex(key1); + }else if(inputType=="A2"){ + // let key = res.content; + lastHex = key; + if(key.length%2==1){ + lastHex = "0"+key; + } + }else if(inputType=="A3"){ + + } + + let contentLengh = lastHex.length/2; + let lenghHex = that.get10to16Hex(contentLengh); + order = "ccc3"+inputType+lenghHex+lastHex; + that.sendCommonOrder(order,true); + console.log('用户点击确定') + } else if (res.cancel) { + order = "ccc30055"; + that.sendCommonOrder(order,true); + console.log('用户点击取消') + } + } + }) + }else if(realOrder =="A4"){ + + wx.showLoading({ + title: message, + }) + + }else if(realOrder =="A5"){ + wx.showLoading({ + title: message, + }) + } + + +},sendCommonOrder:function (hexString,isAllHex){ + let hex = hexString; + if(isAllHex){ + hex = this.buildHex(hexString); + }else{ + hex = hexString; + } + // const hex = this.buildHex(hexString); + let upHex = hex.toUpperCase(); + console.log("发送指令:", upHex); + var buffer = new ArrayBuffer(1); + var dataView = new DataView(buffer); + dataView.setUint8(0, 0x01); // 示例数据 + const bbbb = hexStringToArrayBuffer(upHex); + wx.writeBLECharacteristicValue({ + // AA60000005 + deviceId: getApp().globalData.deviceId, + serviceId: getApp().globalData.serviceUuid, + characteristicId: getApp().globalData.writeUuid, + value: bbbb, + success: function(res) { + console.log('发送成功'); + }, + fail: function(err) { + console.error('发送失败', err); + } + }); + // HEX转ArrayBuffer工具函数 +function hexStringToArrayBuffer(hex) { +const bytes = new Uint8Array(hex.match(/[\da-f]{2}/gi).map(h => parseInt(h, 16))); +return bytes.buffer; +} +},sendBlueOrder:function(e){ + const oder = "aa"+e.currentTarget.dataset.name; + // let oo = "aa60000001"; + // let oo = "aa53000009"; + this.sendCommonOrder(oder,true); + +}, +startNotifi:function(){ + + // 启用通知 + + var app = getApp(); +wx.notifyBLECharacteristicValueChange({ +deviceId: app.globalData.deviceId, +serviceId: '0000ffe0-0000-1000-8000-00805f9b34fb', +characteristicId: '0000ffe1-0000-1000-8000-00805f9b34fb', +state: true, +success: function(res) { + console.log('启用通知成功'); +} +}); + +// 监听数据 +wx.onBLECharacteristicValueChange(function(res) { +console.log('收到数据', res.value); +}); +}, +getDevice:function(){ + wx.getBLEDeviceServices({ + deviceId: app.globalData.deviceId, + success: function(res) { + res.services.forEach(function(service) { + wx.getBLEDeviceCharacteristics({ + deviceId: app.globalData.deviceId, + serviceId: service.uuid, + success: function(res) { + console.log('特征值列表', res.characteristics); + } + }); + }); + } + }); +} + + +}) \ No newline at end of file diff --git a/pages/newdetail/newdetail.json b/pages/newdetail/newdetail.json new file mode 100644 index 0000000..a1328df --- /dev/null +++ b/pages/newdetail/newdetail.json @@ -0,0 +1,6 @@ +{ + "usingComponents": { + + }, + "navigationBarTitleText": "OBD分类" +} diff --git a/pages/newdetail/newdetail.wxml b/pages/newdetail/newdetail.wxml new file mode 100644 index 0000000..7ce93a5 --- /dev/null +++ b/pages/newdetail/newdetail.wxml @@ -0,0 +1,9 @@ + + + 演示视频 + + + 增加钥匙/{{commandMap[0].value}} + + 联系客服:{{support}} + \ No newline at end of file diff --git a/pages/newdetail/newdetail.wxss b/pages/newdetail/newdetail.wxss new file mode 100644 index 0000000..149f26f --- /dev/null +++ b/pages/newdetail/newdetail.wxss @@ -0,0 +1,19 @@ +.container1 { + display: flex; + flex-direction: column; + background-color: #f5f5f5; +} + + +.text-item{ + font-size: 30rpx; + color: #333; + height: 100rpx; + align-items: center; + display: flex; +} +.divider { + width: 100%; + height: 1rpx; + background-color: #ccc; +} \ No newline at end of file diff --git a/pages/newzk/newlist.js b/pages/newzk/newlist.js new file mode 100644 index 0000000..2c87741 --- /dev/null +++ b/pages/newzk/newlist.js @@ -0,0 +1,56 @@ +Page({ + data: { + list:[], + children:[], + codeList:[] + }, + // 在生命周期函数中加载数据 +onLoad(options) { + wx.showToast({ + title: `点击了子项 ${options.name}`, + icon: 'none' + }); + const s = options.name; + wx.request({ + url: 'https://keytest.aik518.com/prod-api/work_key/obd/zk_key/type_blue?blueName='+"NISSAN_OBD", + method: 'GET', // 请求方法 + success: res => { + this.setData({ + children:res.data.data[0].children + // list: res.data.map(item => ({ + // ...item, + // isExpanded: false, + // bgColor: '#fff' + // })) + }) + } + }) +}, +refresh: function(e) { + let id = e.currentTarget.dataset; + wx.navigateTo({ + url: '/pages/newdetail/newdetail?id=1&name=example' + }); +}, +// 点击事件处理函数 +handleItemClick: function (e) { + // const index = e.currentTarget.dataset.index; // 主列表索引 + // const subIndex = e.currentTarget.dataset.subIndex; // 子列表索引 + // const subItem = this.data.list[index].sublist[subIndex]; + // wx.showToast({ title: `点击了 ${subItem.name}`, icon: 'none' }); + const dataid = e.currentTarget.dataset.id; + // const {parentIndex, childIndex} = e.currentTarget.dataset + // const childData = this.data.list[parentIndex].children[childIndex] + // const childId = e.currentTarget.dataset.child; + + wx.navigateTo({ + url: '/pages/newdetail/newdetail?name='+dataid // 支持参数传递 + }) + wx.showToast({ + title: `点击了子项 ${dataid}`, + icon: 'none' + }); +}, + + +}) \ No newline at end of file diff --git a/pages/newzk/newlist.json b/pages/newzk/newlist.json new file mode 100644 index 0000000..a1328df --- /dev/null +++ b/pages/newzk/newlist.json @@ -0,0 +1,6 @@ +{ + "usingComponents": { + + }, + "navigationBarTitleText": "OBD分类" +} diff --git a/pages/newzk/newlist.wxml b/pages/newzk/newlist.wxml new file mode 100644 index 0000000..af8532c --- /dev/null +++ b/pages/newzk/newlist.wxml @@ -0,0 +1,11 @@ + + + {{item.name}} + + \ No newline at end of file diff --git a/pages/newzk/newlist.wxss b/pages/newzk/newlist.wxss new file mode 100644 index 0000000..9594296 --- /dev/null +++ b/pages/newzk/newlist.wxss @@ -0,0 +1,8 @@ +.list-item { + padding: 20rpx; + border-bottom: 1rpx solid #eee; + background-color: #fff; +} +.list-item:hover { + background-color: #f5f5f5; +} \ No newline at end of file diff --git a/pages/playvideo/video.js b/pages/playvideo/video.js new file mode 100644 index 0000000..2c90102 --- /dev/null +++ b/pages/playvideo/video.js @@ -0,0 +1,57 @@ +Page({ + data: { + videoSrc: '' // Replace with your video URL + }, + onLoad(options) { + const s = decodeURIComponent(options.url); + wx.showToast({ + title: `点击了子项 ${s}`, + icon: 'none' + }); + + + console.log(s); + this.setData({ + videoSrc:s + }) + + + }, + + onReady() { + // Create video context + this.videoContext = wx.createVideoContext('myVideo'); + }, + + // Play video + playVideo() { + this.videoContext.play(); + }, + + // Pause video + pauseVideo() { + this.videoContext.pause(); + }, + + // Event handlers + onVideoPlay() { + wx.showToast({ + title: '视频开始播放', + icon: 'none' + }); + }, + + onVideoPause() { + wx.showToast({ + title: '视频已暂停', + icon: 'none' + }); + }, + + onVideoEnded() { + wx.showToast({ + title: '视频播放结束', + icon: 'none' + }); + } +}); \ No newline at end of file diff --git a/pages/playvideo/video.json b/pages/playvideo/video.json new file mode 100644 index 0000000..e370e5f --- /dev/null +++ b/pages/playvideo/video.json @@ -0,0 +1,6 @@ +{ + "usingComponents": { + + }, + "navigationBarTitleText": "视频教程" +} diff --git a/pages/playvideo/video.wxml b/pages/playvideo/video.wxml new file mode 100644 index 0000000..5f0ebd5 --- /dev/null +++ b/pages/playvideo/video.wxml @@ -0,0 +1,16 @@ + + + + + + + \ No newline at end of file diff --git a/pages/playvideo/video.wxss b/pages/playvideo/video.wxss new file mode 100644 index 0000000..c4be0e3 --- /dev/null +++ b/pages/playvideo/video.wxss @@ -0,0 +1,22 @@ +.container { + padding: 20rpx; + display: flex; + flex-direction: column; + align-items: center; +} + +.video-player { + width: 100%; + height: 450rpx; + margin-bottom: 20rpx; +} + +.controls { + display: flex; + gap: 20rpx; +} + +button { + font-size: 32rpx; + padding: 20rpx 40rpx; +} \ No newline at end of file diff --git a/pages/service/service.js b/pages/service/service.js new file mode 100644 index 0000000..9f16c0b --- /dev/null +++ b/pages/service/service.js @@ -0,0 +1,158 @@ +Page({ + data: { + deviceList: [], + isSearching: false + }, + + // 开始搜索 请给出一个微信小程序可展开列表并且展开后是一个列表,并且宽度占满屏幕,展开后父列表改变背景颜色,缩起其它已展开的列表的示例代码 + startSearch() { + + + if (this.data.isSearching) return; + + wx.openBluetoothAdapter({ + mode: 'central', // ios必须要带这个参数 + success: () => { + this.setData({ isSearching: true }); + this.startDiscovery(); + }, + fail: (err) => { + wx.showToast({ title: '请开启手机蓝牙', icon: 'none' }); + console.error('蓝牙初始化失败:', err); + } + }); + + // wx.navigateTo({ + // // url: '/pages/datalist/brand?name='+'FORD_OBD' // 支持参数传递 + // url: '/pages/newzk/newlist?name='+"FORD_OBD" // 支持参数传递 + // }) + // wx.showToast({ title: '连接成功'+deviceName, icon: 'success' }); + + }, + + // 启动设备发现 + startDiscovery() { + wx.startBluetoothDevicesDiscovery({ + allowDuplicatesKey: false, + success: () => { + this.listenDevices(); + setTimeout(() => this.stopDiscovery(), 7000); // 10秒后停止搜索 + }, + fail: (err) => { + console.error('搜索启动失败:', err); + this.setData({ isSearching: false }); + } + }); + }, + + // 监听发现设备 + listenDevices() { + wx.onBluetoothDeviceFound((res) => { + const newDevices = res.devices.filter(device => + !this.data.deviceList.some(d => d.deviceId === device.deviceId), + ); + + // const blename = newDevices.blename + // if(blename.includes('FORD')){ + const filteredDevices = newDevices.filter(device => + device.name.includes("OBD"), + ); + // this.setData({ devices: filteredDevices }); + // } + this.setData({ + deviceList: [...this.data.deviceList, ...filteredDevices] + }); + + }); + }, + + // 停止搜索 + stopDiscovery() { + wx.stopBluetoothDevicesDiscovery({ + success: () => { + this.setData({ isSearching: false }); + // wx.showToast({ title: '搜索完成', icon: 'success' }); + } + }); + }, + + // 设备连接(示例框架) + connectDevice(e) { + + const deviceId = e.currentTarget.dataset.deviceid; + const deviceName = e.currentTarget.dataset.blename; + const app = getApp(); + + + wx.createBLEConnection({ + deviceId, + success: () => { + getApp().globalData.deviceId = deviceId; + + wx.navigateTo({ + // url: '/pages/datalist/brand?name='+deviceName // 支持参数传递 + url: '/pages/newzk/newlist?name='+deviceName // 支持参数传递 + }) + + // this.discoverServices(deviceId); + wx.showToast({ title: '连接成功'+deviceName+deviceId, icon: 'success' }); + } + + + }); + + wx.onBLEConnectionStateChange((res) => { + if (!res.connected) { + console.log("设备已断开,尝试重连..."); + this.reconnectDevice(); // 触发重连逻辑 + } + }); + }, + discoverServices(deviceId) { + wx.getBLEDeviceServices({ + deviceId: deviceId, + success: (res) => { + // 假设目标服务UUID为 FEE0(需替换为实际值) + const targetServiceId = res.services.find(s => s.uuid.toLowerCase() === '0000ffe0-0000-1000-8000-00805f9b34fb').uuid; + getApp().globalData.serviceUuid = targetServiceId; + + console.log('可写特征值:', targetServiceId); + this.getCharacteristics(deviceId, targetServiceId); + }, + fail: (err) => console.error('发现服务失败', err) + }); + }, + getCharacteristics(deviceId, serviceId) { + wx.getBLEDeviceCharacteristics({ + deviceId: deviceId, + serviceId: serviceId, + success: (res) => { + const characteristics = res.characteristics; + let writeUuid = '', notifiUuid = ''; + + characteristics.forEach(char => { + if (char.properties.writeNoResponse)getApp().globalData.writeUuid = char.uuid; // 可写特征 + if(char.properties.notify){ + getApp().globalData.notifiUuid = char.uuid; + } + // if (char.properties.) getApp().globalData.notifyUUID = char.uuid; // 通知特征 + }); + + console.log('可写特征值:', writeUuid); + console.log('通知特征值:', notifiUuid); + + // 启用通知(必须) + if (notifyUUID) { + wx.notifyBLECharacteristicValueChange({ + deviceId: deviceId, + serviceId: serviceId, + characteristicId: notifyUUID, + state: true, + success: () => console.log('通知已启用') + }); + } + }, + fail: (err) => console.error('获取特征值失败', err) + }); + } +}) diff --git a/pages/service/service.json b/pages/service/service.json new file mode 100644 index 0000000..af52a92 --- /dev/null +++ b/pages/service/service.json @@ -0,0 +1,5 @@ +{ + "usingComponents": { + }, + "navigationBarTitleText": "CAR匹配" +} \ No newline at end of file diff --git a/pages/service/service.wxml b/pages/service/service.wxml new file mode 100644 index 0000000..d8dd6fa --- /dev/null +++ b/pages/service/service.wxml @@ -0,0 +1,41 @@ + + + + 请确保OBD设备已经连接汽车OBD接口,并未与其它手机连接 + + + + + + + + + {{item.name || '未知设备'}} + {{item.deviceId}} + + + + {{item.RSSI}} + + + + + + \ No newline at end of file diff --git a/pages/service/service.wxss b/pages/service/service.wxss new file mode 100644 index 0000000..8f5bee4 --- /dev/null +++ b/pages/service/service.wxss @@ -0,0 +1,81 @@ +.container { + padding: 20rpx; +} + +.device-item { + display: flex; + justify-content: space-between; + align-items: center; + padding: 20rpx; + border-bottom: 1rpx solid #e0e0e0; +} + +.device-info { + display: flex; + align-items: center; +} + +.bluetooth-icon { + width: 40rpx; + height: 40rpx; + margin-right: 20rpx; +} + +.device-details { + display: flex; + flex-direction: column; +} + +.device-name { + font-size: 38rpx; + color: #333; + +} + +.device-mac { + font-size: 24rpx; + color: #999; + +} + +.device-status { + display: flex; + align-items: center; +} + +.signal-strength { + font-size: 28rpx; + color: #666; + margin-right: 10rpx; +} + +.signal-icon { + width: 30rpx; + height: 30rpx; + margin-right: 20rpx; +} + +.connect-btn { + background-color: #07c160; + color: #fff; + font-size: 25rpx; + text-align: center; + border-radius: 8rpx; + padding: 0; + white-space: nowrap; + overflow: hidden; +} +.s-button{ + display: block; + width: 100%; +} +.device-notice{ + display: block; + font-size: 20rpx; + display: flex; + align-items: center; /* 垂直居中 */ + justify-content: center; /* 水平居中 */ + color: #666; + margin-top: 10rpx; + margin-bottom: 10rpx; +} \ No newline at end of file diff --git a/project.config.json b/project.config.json new file mode 100644 index 0000000..a9801ed --- /dev/null +++ b/project.config.json @@ -0,0 +1,29 @@ +{ + "compileType": "miniprogram", + "libVersion": "trial", + "setting": { + "coverView": true, + "es6": true, + "postcss": true, + "minified": true, + "enhance": true, + "showShadowRootInWxmlPanel": true, + "packNpmRelationList": [], + "babelSetting": { + "ignore": [], + "disablePlugins": [], + "outputPath": "" + }, + "ignoreUploadUnusedFiles": true + }, + "condition": {}, + "editorSetting": { + "tabIndent": "auto", + "tabSize": 2 + }, + "packOptions": { + "ignore": [], + "include": [] + }, + "appid": "wx6f0537b3e4a81c93" +} \ No newline at end of file diff --git a/project.private.config.json b/project.private.config.json new file mode 100644 index 0000000..1dbd815 --- /dev/null +++ b/project.private.config.json @@ -0,0 +1,48 @@ +{ + "description": "项目私有配置文件。此文件中的内容将覆盖 project.config.json 中的相同字段。项目的改动优先同步到此文件中。详见文档:https://developers.weixin.qq.com/miniprogram/dev/devtools/projectconfig.html", + "projectname": "obd_special", + "setting": { + "compileHotReLoad": true + }, + "condition": { + "miniprogram": { + "list": [ + { + "name": "pages/index/index", + "pathName": "pages/index/index", + "query": "", + "launchMode": "default", + "scene": null + }, + { + "name": "pages/index/index", + "pathName": "pages/index/index", + "query": "", + "launchMode": "default", + "scene": null + }, + { + "name": "pages/service/service", + "pathName": "pages/service/service", + "query": "", + "launchMode": "default", + "scene": null + }, + { + "name": "pages/index/index", + "pathName": "pages/index/index", + "query": "", + "launchMode": "default", + "scene": null + }, + { + "name": "pages/newdetail/newdetail", + "pathName": "pages/newdetail/newdetail", + "query": "", + "launchMode": "default", + "scene": null + } + ] + } + } +} \ No newline at end of file diff --git a/sitemap.json b/sitemap.json new file mode 100644 index 0000000..ca02add --- /dev/null +++ b/sitemap.json @@ -0,0 +1,7 @@ +{ + "desc": "关于本文件的更多信息,请参考文档 https://developers.weixin.qq.com/miniprogram/dev/framework/sitemap.html", + "rules": [{ + "action": "allow", + "page": "*" + }] +} \ No newline at end of file diff --git a/utils/bleUtil.js b/utils/bleUtil.js new file mode 100644 index 0000000..dc8e281 --- /dev/null +++ b/utils/bleUtil.js @@ -0,0 +1,582 @@ +import { log } from './log.js' +const TAG = 'bleUtil-' +const logEnable = false +let isAndroid = false +let ecBluetoothAdapterStateChangeCallback = () => { } +let ecBLEConnectionStateChangeCallback = () => { } +let ecBLECharacteristicValueChangeCallBack = () => { } + +let ecDeviceId = '' +// 主服务 +let serverUUID = '' +const serverUUID1 = '0000FFF0-0000-1000-8000-00805F9B34FB' +const serverUUID2 = 'FFF0' +// 写 +let gattCharacteristicWriteUUID = '' +const writeUUID1 = '0000FFF3-0000-1000-8000-00805F9B34FB' +const writeUUID2 = 'FFF3' +// 读(电池电量) +let gattCharacteristicReadUUID = '' +const readUUID = '0000FFF5-0000-1000-8000-00805F9B34FB' +const readUUID2 = 'FFF5' +/** +errCode: 30001:'请打开系统蓝牙开关', +errCode: 30002:'请打开系统定位开关', +errCode: 30003:'请打开微信定位权限,允许微信使用您的位置信息', +errCode: 30004:'请打开小程序蓝牙开关,点击右上角三个点,然后点击设置', +errCode: 30005: '蓝牙适配器不可用', +errCode: 30006: '微信小程序蓝牙权限未获取', +errCode: 30000: '微信小程序设置页面打开失败', +errCode: 30007: '微信小程序授权失败', +errCode: 30008:'openBluetoothAdapter 失败', +errCode: 30009:'通知失败', +errCode: 30010: '发送指令为空', +errCode: 30011: 'deviceId为空', +errCode: 10013: '连接失败' + */ +const bleLog = data => { + if (logEnable) { + log(TAG + JSON.stringify(data)) + } +} + +const onBluetoothAdapterStateChange = cb => { + ecBluetoothAdapterStateChangeCallback = cb +} +const onBLECharacteristicValueChange = cb => { + ecBLECharacteristicValueChangeCallBack = cb +} +const getSetting = () => { + return new Promise(function (resolve, reject) { + wx.getSetting({ + success(res) { + if (res.authSetting && res.authSetting['scope.bluetooth']) { + resolve({ ok: true, errCode: 0, errMsg: '' }) + } else { + resolve({ + ok: false, + errCode: 30006, + errMsg: 'scope.bluetooth false', + }) + } + }, + fail(res) { + resolve({ + ok: false, + errCode: res.errCode ? res.errCode : 30000, + errMsg: res.errMsg ? res.errMsg : 'getSetting fail', + }) + }, + }) + }) +} +const authorize = () => { + return new Promise(function (resolve, reject) { + wx.authorize({ + scope: 'scope.bluetooth', + success(res) { + resolve({ ok: true, errCode: 0, errMsg: '' }) + }, + fail(res) { + // {"errMsg":"authorize:fail:auth deny"} + resolve({ ok: false, errCode: 30007, errMsg: res.errMsg }) + }, + }) + }) +} +const _openBluetoothAdapter = () => { + return new Promise(function (resolve, reject) { + wx.openBluetoothAdapter({ + success(res) { + // {errno: 0, errMsg: "openBluetoothAdapter:ok"} + resolve({ ok: true, errCode: 0, errMsg: '' }) + }, + fail(res) { + resolve({ + ok: false, + errCode: res.errCode ? res.errCode : 30008, + errMsg: res.errMsg, + }) + }, + }) + }) +} +const openBluetoothAdapter = async () => { + await _openBluetoothAdapter() + // 平台 + let platform + // 蓝牙开关 + let bluetoothEnabled + // 位置开关 + let locationEnabled + // 定位权限 + let locationAuthorized + if (wx.canIUse('getSystemInfoSync')) { + const systemInfo = wx.getSystemInfoSync() + platform = systemInfo.platform.toLowerCase() + bluetoothEnabled = systemInfo.bluetoothEnabled + locationEnabled = systemInfo.locationEnabled + locationAuthorized = systemInfo.locationAuthorized + } else { + const deviceInfo = wx.getDeviceInfo() + platform = deviceInfo.platform + const systemSetting = wx.getSystemSetting() + bluetoothEnabled = systemSetting.bluetoothEnabled + locationEnabled = systemSetting.locationEnabled + const authorizedSetting = wx.getAppAuthorizeSetting() + locationAuthorized = authorizedSetting.locationAuthorized + } + //log(`platform = ${platform} , bluetoothEnabled = ${bluetoothEnabled} , locationEnabled = ${locationEnabled} , locationAuthorized = ${locationAuthorized}`) + if (platform === 'android') { + isAndroid = true + } + if (!bluetoothEnabled) { + ecBluetoothAdapterStateChangeCallback({ + ok: false, + errCode: 30001, + errMsg: '请打开系统蓝牙开关', + }) + return + } + if (isAndroid && !locationEnabled) { + ecBluetoothAdapterStateChangeCallback({ + ok: false, + errCode: 30002, + errMsg: '请打开系统定位开关', + }) + return + } + if (isAndroid && !locationAuthorized) { + ecBluetoothAdapterStateChangeCallback({ + ok: false, + errCode: 30003, + errMsg: '请打开微信定位权限,允许微信使用您的位置信息', + }) + return + } + const setting = await getSetting() //小程序蓝牙权限 + if (!setting.ok) { + const authRes = await authorize() + if (!authRes.ok) { + ecBluetoothAdapterStateChangeCallback({ + ok: false, + errCode: 30004, + errMsg: '请打开小程序蓝牙开关,点击右上角三个点,然后点击设置', + }) + return + } + } + wx.offBluetoothAdapterStateChange() + wx.onBluetoothAdapterStateChange(res => { + //log(res) + // {available: true, discovering: true} + if (!res.available) { + ecBluetoothAdapterStateChangeCallback({ + ok: false, + errCode: 30005, + errMsg: '蓝牙适配器不可用', + }) + } + }) + const openRes = await _openBluetoothAdapter() + ecBluetoothAdapterStateChangeCallback(openRes) +} + +const onBluetoothDeviceFound = cb => { + wx.offBluetoothDeviceFound() + wx.onBluetoothDeviceFound(res => { + // log(res) + const device = res.devices[0] + const name = device.name ? device.name : device.localName + if (!name) { + return + } + let id = device.deviceId + let rssi = device.RSSI + cb({ id, name, rssi }) + }) +} + +let scanFlag = false +const startBluetoothDevicesDiscovery = () => { + if (scanFlag === false) { + log(TAG + 'start scan') + wx.startBluetoothDevicesDiscovery({ + //services: [serverId], // 传入这个参数,只搜索主服务 UUID 为 serverId 的设备 + allowDuplicatesKey: true, + powerLevel: 'high', + complete(res) { + // log(res) + }, + }) + scanFlag = true + } +} +const stopBluetoothDevicesDiscovery = () => { + if (scanFlag) { + wx.stopBluetoothDevicesDiscovery({ + complete(res) { + log(TAG + 'stop scan') + }, + }) + scanFlag = false + } +} + +const onBLEConnectionStateChange = cb => { + ecBLEConnectionStateChangeCallback = cb +} +/** + * 连接蓝牙低功耗设备 + * + */ +const _createBLEConnection = () => { + return new Promise(function (resolve, reject) { + wx.createBLEConnection({ + deviceId: ecDeviceId, + success(res) { + // {"errno":0,"errCode":0,"errMsg":"createBLEConnection:ok"} + resolve({ ok: true, errCode: 0, errMsg: '' }) + }, + fail(res) { + // {"errno":1001,"errMsg":"createBLEConnection:fail parameter error: parameter.deviceId should be String instead of Undefined;"} + resolve({ + ok: false, + errCode: res.errCode ? res.errCode : res.errno, + errMsg: res.errMsg, + }) + }, + }) + }) +} +/** + * 获取蓝牙低功耗设备所有服务 (service) + * @param { deviceId } 蓝牙设备 id。需要已经通过 wx.createBLEConnection 建立连接 + */ +const getBLEDeviceServices = () => { + return new Promise(function (resolve, reject) { + wx.getBLEDeviceServices({ + deviceId: ecDeviceId, + success(res) { + log(res) + //{"services":[{"uuid":"0000FFF0-0000-1000-8000-00805F9B34FB","isPrimary":true}],"errCode":0,"errno":0,"errMsg":"getBLEDeviceServices:ok"} + // {"errno":0,"deviceId":"7C7E20F2-CB75-6DA8-F8DF-FFF702B0D63F","services":[{"isPrimary":true,"uuid":"0000FFF0-0000-1000-8000-00805F9B34FB"}],"errMsg":"getBLEDeviceServices:ok","errCode":0} + resolve({ + ok: true, + errCode: 0, + errMsg: '', + services: res.services, + }) + }, + fail(res) { + log(res) + resolve({ ok: false, errCode: res.errCode, errMsg: res.errMsg }) + }, + }) + }) +} + +/** + * 获取蓝牙低功耗设备某个服务中所有特征 (characteristic) + * @param { deviceId } 蓝牙设备 id。需要已经通过 wx.createBLEConnection 建立连接 + * @param { serviceId } 蓝牙服务 UUID。需要先调用 wx.getBLEDeviceServices 获取 + */ +const getBLEDeviceCharacteristics = serviceId => { + return new Promise(function (resolve, reject) { + wx.getBLEDeviceCharacteristics({ + deviceId: ecDeviceId, + serviceId, + success(res) { + log(TAG + res) + // {"characteristics":[{"uuid":"0000FFF2-0000-1000-8000-00805F9B34FB","handle":3,"properties":{"read":false,"write":true,"notify":false,"indicate":false,"writeNoResponse":true,"writeDefault":true}},{"uuid":"0000FFF1-0000-1000-8000-00805F9B34FB","handle":5,"properties":{"read":true,"write":true,"notify":true,"indicate":false,"writeNoResponse":true,"writeDefault":true}}],"errCode":0,"errno":0,"errMsg":"getBLEDeviceCharacteristics:ok"} + // {"characteristics":[{"properties":{"writeDefault":true,"notify":false,"write":true,"indicate":false,"read":false,"writeNoResponse":true},"uuid":"0000FFF2-0000-1000-8000-00805F9B34FB"},{"properties":{"writeDefault":true,"notify":true,"write":true,"indicate":false,"read":true,"writeNoResponse":true},"uuid":"0000FFF1-0000-1000-8000-00805F9B34FB"}],"deviceId":"7C7E20F2-CB75-6DA8-F8DF-FFF702B0D63F","serviceId":"0000FFF0-0000-1000-8000-00805F9B34FB","errno":0,"errMsg":"getBLEDeviceCharacteristics:ok","errCode":0} + resolve({ + ok: true, + errCode: 0, + errMsg: '', + characteristics: res.characteristics, + }) + }, + fail(res) { + log(TAG + res) + resolve({ ok: false, errCode: res.errCode, errMsg: res.errMsg }) + }, + }) + }) +} +// 启用蓝牙低功耗设备特征值变化时的 notify 功能,订阅特征。注意:必须设备的特征支持 notify 或者 indicate 才可以成功调用。 +// 另外,必须先启用 wx.notifyBLECharacteristicValueChange 才能监听到设备 characteristicValueChange 事 +const notifyBLECharacteristicValueChange = (serviceId, characteristicId) => { + return new Promise(function (resolve, reject) { + wx.notifyBLECharacteristicValueChange({ + state: true, + deviceId: ecDeviceId, + serviceId, + characteristicId, + success(res) { + bleLog(res) + // {"errCode":0,"errno":0,"errMsg":"notifyBLECharacteristicValueChange:ok"} + resolve({ ok: true, errCode: 0, errMsg: '' }) + }, + fail(res) { + bleLog(res) + resolve({ ok: false, errCode: res.errCode, errMsg: res.errMsg }) + }, + }) + }) +} +const setBLEMTU = mtu => { + return new Promise(function (resolve, reject) { + wx.setBLEMTU({ + deviceId: ecDeviceId, + mtu, + success(res) { + bleLog(res) + // {"errMsg":"setBLEMTU:ok","errno":0,"errCode":0,"mtu":50} + resolve({ ok: true, errCode: 0, errMsg: '' }) + }, + fail(res) { + bleLog(res) + // {"errCode":-1,"errno":1500104,"errMsg":"setBLEMTU:fail:internal error"} + resolve({ ok: false, errCode: res.errCode, errMsg: res.errMsg }) + }, + }) + }) +} +//和设备建立连接 +const createBLEConnection = async id => { + bleLog('id = ' +id) + ecDeviceId = id + wx.offBLEConnectionStateChange() + wx.onBLEConnectionStateChange(async res => { + // {"deviceId":"EC:22:05:13:78:49","connected":true} + if (res.connected) { + // 获取所有服务 + const servicesResult = await getBLEDeviceServices() + if (!servicesResult.ok) { + // 服务获取失败 + ecBLEConnectionStateChangeCallback(servicesResult) + closeBLEConnection() + return + } + // 遍历服务 + for (const service of servicesResult.services) { + if ((service.uuid.toUpperCase() === serverUUID1) || + (service.uuid.toUpperCase() === serverUUID2)) { + // 获得服务的uuid + serverUUID = service.uuid + } + // 通过服务的uuid,获取特征值的uuid + const characteristicsResult = await getBLEDeviceCharacteristics(service.uuid) + if (!characteristicsResult.ok) { + // 特征值获取失败 + ecBLEConnectionStateChangeCallback(characteristicsResult) + closeBLEConnection() + return + } + for (const characteristic of characteristicsResult.characteristics) { + // 通知 + if (characteristic.properties && characteristic.properties.notify) { + const notifyResult = await notifyBLECharacteristicValueChange(service.uuid, characteristic.uuid) + bleLog('通知结果 = ' + notifyResult) + if (!notifyResult.ok) { + ecBLEConnectionStateChangeCallback({ + ok: false, + errCode: 30009, + errMsg: 'notify error', + }) + closeBLEConnection() + return + } + } + // 读(电池电量) + if ((characteristic.uuid.toUpperCase() === readUUID) || (characteristic.uuid.toUpperCase() === readUUID2)) { + gattCharacteristicReadUUID = characteristic.uuid + } + // 写 + if ((characteristic.uuid.toUpperCase() === writeUUID1) || (characteristic.uuid.toUpperCase() === writeUUID2)) { + gattCharacteristicWriteUUID = characteristic.uuid + } + } + } + if (isAndroid) { + await setBLEMTU(247) + } + ecBLEConnectionStateChangeCallback({ + ok: true, + errCode: 0, + errMsg: '', + }) + } else { + ecBLEConnectionStateChangeCallback({ + ok: false, + errCode: 10001, + errMsg: 'disconnect', + }) + } + }) + const res = await _createBLEConnection() + if (!res.ok) { + ecBLEConnectionStateChangeCallback({ + ok: false, + errCode: 10013, + errMsg: res.errMsg, + }) + } +} +//关闭当前连接 +const closeBLEConnection = id => { + if (isValueEmpty(id)) { + bleLog('id is empty') + return + } + wx.closeBLEConnection({ + deviceId: id, + complete(res) { + bleLog(res) + }, + }) +} + +// 向蓝牙低功耗设备特征值中写入二进制数据 +const writeBLECharacteristicValue = (str) => { + if (str.length === 0) { + return { ok: false, errCode: 30010, errMsg: 'data is null' } + } + if (isValueEmpty(ecDeviceId)) { + return { ok: false, errCode: 30011, errMsg: 'ecDeviceId is null' } + } + let buffer = new Uint8Array(hexStrToBytes(str)).buffer + return _writeBLECharacteristicValue(buffer) +} + +const _writeBLECharacteristicValue = buffer => { + return new Promise(function (resolve, reject) { + wx.writeBLECharacteristicValue({ + deviceId: ecDeviceId, + serviceId: serverUUID, + characteristicId: gattCharacteristicWriteUUID, + value: buffer, + writeType: 'writeNoResponse', + success(res) { + // console.log(res) + resolve({ ok: true, errCode: 0, errMsg: '' }) + }, + fail(res) { + //console.log(res) + resolve({ ok: false, errCode: res.errCode, errMsg: res.errMsg }) + }, + }) + }) +} + +// 读取蓝牙设备中的数据 +const readBLECharacteristicValue = () => { + wx.onBLECharacteristicValueChange((characteristic) => { + console.log(characteristic) + // 获取特征值的 ArrayBuffer + const buffer = characteristic.value; + const value = new Uint8Array(buffer) + const hex = arrayBufferToHex(value) + ecBLECharacteristicValueChangeCallBack({ + ok: true, + errCode: 10014, + errMsg: hex, + }) + }) + wx.readBLECharacteristicValue({ + characteristicId: gattCharacteristicReadUUID, + deviceId: ecDeviceId, + serviceId: serverUUID, + success(res) { + bleLog(res) + }, + fail(res) { + bleLog(res) + }, + }) +} + +// 转换 ArrayBuffer 为 16 进制字符串 +function arrayBufferToHex(buffer) { + const uint8Array = new Uint8Array(buffer); + let hexString = ''; + uint8Array.forEach(byte => { + hexString += byte.toString(16).padStart(2, '0'); + }); + return hexString; +} + +// bytes 转 16进制字符窜 +const bytesToHexStr = bytes => { + let strHex = '' + for (let i = 0; i < bytes.length; i++) { + strHex = strHex + bytes[i].toString(16).padStart(2, '0').toUpperCase() + } + return strHex +} + +/** + * 16进制字符串转bytes + * @param hexStr 16进制字符串 + */ +const hexStrToBytes = hexStr => { + let bytes = [] + for (let i = 0; i < (hexStr.length / 2); i++) { + bytes.push(parseInt(hexStr.substr(2 * i, 2), 16)) + } + return bytes +} + +// int 转16进制 +function intToHexString(number) { + let hexString = number.toString(16) + // toString(16).padStart(2, '0') 这个api需要传入转16进制的指定长度 + if (hexString.length % 2 != 0) { + hexString = '0' + hexString + } + return hexString +} + +// 判空 +function isValueEmpty(value) { + if (value === null || value === undefined) { + return true; + } + + if (typeof value === 'string') { + return !value.trim().length; + } + + if (Array.isArray(value)) { + return value.length === 0; + } + + if (typeof value === 'object') { + return Object.keys(value).length === 0; + } + return false; +} + +// 16进制字符串倒序输出 +function reverseHexString(hexString) { + // 将16进制字符串按字节分割并倒序 + let reversedHexString = hexString.match(/.{1,2}/g).reverse().join(''); + return reversedHexString; +} + +module.exports = { + onBluetoothAdapterStateChange, + openBluetoothAdapter, + onBluetoothDeviceFound, + startBluetoothDevicesDiscovery, + stopBluetoothDevicesDiscovery, + onBLEConnectionStateChange, + createBLEConnection, + closeBLEConnection, + onBLECharacteristicValueChange, + writeBLECharacteristicValue, + readBLECharacteristicValue, + intToHexString, + reverseHexString, +} diff --git a/utils/constants.js b/utils/constants.js new file mode 100644 index 0000000..bfec9fb --- /dev/null +++ b/utils/constants.js @@ -0,0 +1,6 @@ +export const BLE_NAME = 'MeetLove' +export const MAIN_COLOR = '#55A0B6' +export const GRAY_4 = '#717882' +export const BLACK_34 = '#343843' +export const BLE_ID = 'ble_id' +export const BACK_TYPE = "back_type" /* 返回类型 1 是按返回键, 2是蓝牙设备自己断开返回 */ \ No newline at end of file diff --git a/utils/image/ic_blue_remote.png b/utils/image/ic_blue_remote.png new file mode 100644 index 0000000..3fb4ef8 Binary files /dev/null and b/utils/image/ic_blue_remote.png differ diff --git a/utils/image/rss_line.png b/utils/image/rss_line.png new file mode 100644 index 0000000..833c217 Binary files /dev/null and b/utils/image/rss_line.png differ diff --git a/utils/log.js b/utils/log.js new file mode 100644 index 0000000..52cd133 --- /dev/null +++ b/utils/log.js @@ -0,0 +1,9 @@ +const logEnable = true +export function log(msg) { + if(logEnable) { + console.log('play : ' + JSON.stringify(msg)) + } +} +export default { + log +} \ No newline at end of file diff --git a/utils/stringUtils.js b/utils/stringUtils.js new file mode 100644 index 0000000..e69de29 diff --git a/utils/timeUtils.js b/utils/timeUtils.js new file mode 100644 index 0000000..18eea3f --- /dev/null +++ b/utils/timeUtils.js @@ -0,0 +1,69 @@ + +/** + * 格式化秒数为“hh:mm:ss”格式 + * @param {number} seconds - 需要格式化的秒数 + * @returns {string} 格式化后的时间字符串 + */ +function formatTime(seconds) { + const hours = Math.floor(seconds / 3600); // 获取小时数 + const minutes = Math.floor((seconds % 3600) / 60); // 获取分钟数 + const secs = seconds % 60; // 获取秒数 + + // 将小时、分钟、秒数格式化为两位数 + const formattedHours = hours < 10 ? `0${hours}` : hours; + const formattedMinutes = minutes < 10 ? `0${minutes}` : minutes; + const formattedSecs = secs < 10 ? `0${secs}` : secs; + + return `${formattedHours}:${formattedMinutes}:${formattedSecs}`; +} + + +/** + * 计算两个时间点之间的差值 + * @param {Date} startTime - 起始时间 + * @param {Date} endTime - 结束时间 + * @returns {number} 时间差(秒) + */ +function getTimeDifference(startTime, endTime) { + const diffInMs = endTime - startTime; + return Math.floor(diffInMs / 1000); // 将毫秒转换为秒 +} + +/** + * 获取当前时间的“hh:mm:ss”格式 + * @returns {string} 当前时间的字符串 + */ +function getCurrentTime() { + const now = new Date(); + const hours = now.getHours(); + const minutes = now.getMinutes(); + const seconds = now.getSeconds(); + + const formattedHours = hours < 10 ? `0${hours}` : hours; + const formattedMinutes = minutes < 10 ? `0${minutes}` : minutes; + const formattedSecs = seconds < 10 ? `0${seconds}` : seconds; + + return `${formattedHours}:${formattedMinutes}:${formattedSecs}`; +} + +function getCurrentHours() { + const now = new Date(); + const hours = now.getHours(); + const formattedHours = hours < 10 ? `0${hours}` : hours; + return `${formattedHours}`; +} + +function getCurrentMinutes() { + const now = new Date(); + const minutes = now.getMinutes(); + const formattedMinutes = minutes < 10 ? `0${minutes}` : minutes; + return `${formattedMinutes}`; +} + +export default { + formatTime, + getTimeDifference, + getCurrentTime, + getCurrentHours, + getCurrentMinutes, +}; \ No newline at end of file diff --git a/utils/util.js b/utils/util.js new file mode 100644 index 0000000..764bc2c --- /dev/null +++ b/utils/util.js @@ -0,0 +1,19 @@ +const formatTime = date => { + const year = date.getFullYear() + const month = date.getMonth() + 1 + const day = date.getDate() + const hour = date.getHours() + const minute = date.getMinutes() + const second = date.getSeconds() + + return `${[year, month, day].map(formatNumber).join('/')} ${[hour, minute, second].map(formatNumber).join(':')}` +} + +const formatNumber = n => { + n = n.toString() + return n[1] ? n : `0${n}` +} + +module.exports = { + formatTime +}