commit
010d7e29b2
47 changed files with 2749 additions and 0 deletions
@ -0,0 +1,14 @@ |
|||||
|
# Windows |
||||
|
[Dd]esktop.ini |
||||
|
Thumbs.db |
||||
|
$RECYCLE.BIN/ |
||||
|
|
||||
|
# macOS |
||||
|
.DS_Store |
||||
|
.fseventsd |
||||
|
.Spotlight-V100 |
||||
|
.TemporaryItems |
||||
|
.Trashes |
||||
|
|
||||
|
# Node.js |
||||
|
node_modules/ |
@ -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
|
||||
|
} |
||||
|
}) |
@ -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" |
||||
|
} |
@ -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; |
||||
|
} |
@ -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' |
||||
|
}); |
||||
|
}, |
||||
|
|
||||
|
|
||||
|
}) |
@ -0,0 +1,6 @@ |
|||||
|
{ |
||||
|
"usingComponents": { |
||||
|
|
||||
|
}, |
||||
|
"navigationBarTitleText": "OBD分类" |
||||
|
} |
@ -0,0 +1,27 @@ |
|||||
|
<scroll-view scroll-y style="height: 100vh;"> |
||||
|
<view |
||||
|
wx:for="{{list}}" |
||||
|
wx:key="id" |
||||
|
class="list-item" |
||||
|
style="background-color: {{item.haveChildren ? 'lightblue' : 'white'}}" |
||||
|
data-index="{{index}}" |
||||
|
bindtap="toggleExpand" |
||||
|
data-id="{{item.id}}" |
||||
|
> |
||||
|
{{item.name}}{{item.haveChildren ? '▼' : '▶'}} |
||||
|
<view wx:if="{{item.haveChildren}}" class="sublist" > |
||||
|
<view |
||||
|
wx:for="{{item.children}}" |
||||
|
wx:for-item="child" |
||||
|
wx:key="id" |
||||
|
catchtap="handleItemClick" |
||||
|
class="sublist-item" |
||||
|
data-parent-index="{{index}}" |
||||
|
data-child-index="{{child}}" |
||||
|
|
||||
|
> |
||||
|
{{child.name}} |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
</scroll-view> |
@ -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; |
||||
|
} |
@ -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); |
||||
|
} |
||||
|
}); |
||||
|
}); |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
}) |
@ -0,0 +1,6 @@ |
|||||
|
{ |
||||
|
"usingComponents": { |
||||
|
|
||||
|
}, |
||||
|
"navigationBarTitleText": "详情" |
||||
|
} |
@ -0,0 +1,3 @@ |
|||||
|
<view wx:for="{{list}}" wx:key="index" class="list-item" bind:tap="sendBlueOrder" data-value="{{item.value}}"> |
||||
|
{{item.key}}{{item.value}} |
||||
|
</view> |
@ -0,0 +1,4 @@ |
|||||
|
.list-item { |
||||
|
padding: 10px; |
||||
|
border-bottom: 1px solid #ccc; |
||||
|
} |
@ -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) |
||||
|
}); |
||||
|
} |
||||
|
}) |
@ -0,0 +1,5 @@ |
|||||
|
{ |
||||
|
"usingComponents": { |
||||
|
}, |
||||
|
"navigationBarTitleText": "CAR匹配" |
||||
|
} |
@ -0,0 +1,41 @@ |
|||||
|
<!--index.wxml--> |
||||
|
|
||||
|
<view class="container1"> |
||||
|
<button class = "s-button" type="primary" bindtap="startSearch">开始扫描</button> |
||||
|
<text class="device-notice">请确保OBD设备已经连接汽车OBD接口,并未与其它手机连接</text> |
||||
|
|
||||
|
|
||||
|
<!-- <view class="gradient-view" style="display: flex; flex-direction: row; align-items: center; margin-top: 20rpx; height: 150rpx;margin-left: 20rpx; margin-right: 20rpx;" bindtap="goToMain"> |
||||
|
<image src="/utils/image/ic_blue_remote.png" mode="aspectFill" style="width: 50rpx; height: 50rpx; margin-left: 10rpx; margin-right: 10rpx;"></image> |
||||
|
<view style="display: flex; flex-direction: column;"> |
||||
|
<text class="textContent" >NISSAN_OBD</text> |
||||
|
<text class="textContent" style="display: flex; font-size: 25rpx; color: rgb(88, 92, 92);">C8:C8:C8:C8:C8:C8</text> |
||||
|
</view> |
||||
|
|
||||
|
<view style="display: flex; flex-direction: row; align-items: center;position: relative; width: 100%;justify-content: flex-end;"> |
||||
|
<text class="device-rssi">-56</text> |
||||
|
<image src="/utils/image/rss_line.png" mode="aspectFill" style="width: 50rpx; height: 50rpx; margin-left: 10rpx; "></image> |
||||
|
<button class="device-name" style="width: 160rpx;">连接</button> |
||||
|
</view> |
||||
|
|
||||
|
|
||||
|
|
||||
|
</view> --> |
||||
|
|
||||
|
<view class="device-list"> |
||||
|
<view |
||||
|
wx:for="{{deviceList}}" |
||||
|
wx:key="deviceId" |
||||
|
class="device-item" |
||||
|
bindtap="connectDevice" |
||||
|
data-deviceid="{{item.deviceId}}" |
||||
|
data-blename="{{item.name}}" |
||||
|
> |
||||
|
<text class="device-name">{{item.name || '未知设备'}}</text> |
||||
|
<text class="device-rssi">信号强度: {{item.RSSI}}dBm</text> |
||||
|
<text class="device-rssi">MAC: {{item.deviceId}}</text> |
||||
|
<button class="device-name" >连接</button> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
@ -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; |
||||
|
} |
@ -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 |
||||
|
} |
||||
|
}) |
||||
|
}) |
||||
|
} |
||||
|
}) |
@ -0,0 +1,4 @@ |
|||||
|
{ |
||||
|
"usingComponents": { |
||||
|
} |
||||
|
} |
@ -0,0 +1,6 @@ |
|||||
|
<!--logs.wxml--> |
||||
|
<scroll-view class="scrollarea" scroll-y type="list"> |
||||
|
<block wx:for="{{logs}}" wx:key="timeStamp" wx:for-item="log"> |
||||
|
<view class="log-item">{{index + 1}}. {{log.date}}</view> |
||||
|
</block> |
||||
|
</scroll-view> |
@ -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); |
||||
|
} |
@ -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.length;i++){ |
||||
|
if(listdata[i].type===content){ |
||||
|
console.log("返回命令====>"+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); |
||||
|
} |
||||
|
}); |
||||
|
}); |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
}) |
@ -0,0 +1,6 @@ |
|||||
|
{ |
||||
|
"usingComponents": { |
||||
|
|
||||
|
}, |
||||
|
"navigationBarTitleText": "OBD分类" |
||||
|
} |
@ -0,0 +1,9 @@ |
|||||
|
<view class="container1"> |
||||
|
<text class="text-item" bindtap="handleTextTap" data-id="{{videoUrl}}" data-name="{{videoUrl}}" wx:key="id"> |
||||
|
演示视频 |
||||
|
</text> |
||||
|
<view class="divider"></view> |
||||
|
<text class="text-item" bindtap="sendBlueOrder" data-key="{{commandMap[0].value}}" data-name="{{commandMap[0].value}}" wx:key="id">增加钥匙/{{commandMap[0].value}}</text> |
||||
|
<view class="divider"></view> |
||||
|
<text class="text-item" bindtap="service" data-id="{{support}}" data-url="{{support}}" wx:key="id">联系客服:{{support}}</text> |
||||
|
</view> |
@ -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; |
||||
|
} |
@ -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' |
||||
|
}); |
||||
|
}, |
||||
|
|
||||
|
|
||||
|
}) |
@ -0,0 +1,6 @@ |
|||||
|
{ |
||||
|
"usingComponents": { |
||||
|
|
||||
|
}, |
||||
|
"navigationBarTitleText": "OBD分类" |
||||
|
} |
@ -0,0 +1,11 @@ |
|||||
|
<view class="list"> |
||||
|
<view |
||||
|
wx:for="{{children}}" |
||||
|
wx:key="id" |
||||
|
class="list-item" |
||||
|
bindtap="handleItemClick" |
||||
|
data-id="{{item.id}}" |
||||
|
> |
||||
|
<text>{{item.name}}</text> |
||||
|
</view> |
||||
|
</view> |
@ -0,0 +1,8 @@ |
|||||
|
.list-item { |
||||
|
padding: 20rpx; |
||||
|
border-bottom: 1rpx solid #eee; |
||||
|
background-color: #fff; |
||||
|
} |
||||
|
.list-item:hover { |
||||
|
background-color: #f5f5f5; |
||||
|
} |
@ -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' |
||||
|
}); |
||||
|
} |
||||
|
}); |
@ -0,0 +1,6 @@ |
|||||
|
{ |
||||
|
"usingComponents": { |
||||
|
|
||||
|
}, |
||||
|
"navigationBarTitleText": "视频教程" |
||||
|
} |
@ -0,0 +1,16 @@ |
|||||
|
<view class="container"> |
||||
|
<video |
||||
|
referrer-policy="origin" |
||||
|
id="myVideo" |
||||
|
src="{{videoSrc}}" |
||||
|
controls |
||||
|
bind:play="onVideoPlay" |
||||
|
bind:pause="onVideoPause" |
||||
|
bind:ended="onVideoEnded" |
||||
|
class="video-player" |
||||
|
></video> |
||||
|
<view class="controls"> |
||||
|
<button bindtap="playVideo">播放</button> |
||||
|
<button bindtap="pauseVideo">暂停</button> |
||||
|
</view> |
||||
|
</view> |
@ -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; |
||||
|
} |
@ -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) |
||||
|
}); |
||||
|
} |
||||
|
}) |
@ -0,0 +1,5 @@ |
|||||
|
{ |
||||
|
"usingComponents": { |
||||
|
}, |
||||
|
"navigationBarTitleText": "CAR匹配" |
||||
|
} |
@ -0,0 +1,41 @@ |
|||||
|
<view class="container111"> |
||||
|
|
||||
|
<button class = "s-button" type="primary" bindtap="startSearch">开始扫描</button> |
||||
|
<text class="device-notice">请确保OBD设备已经连接汽车OBD接口,并未与其它手机连接</text> |
||||
|
|
||||
|
<!-- <view class="device-item" > |
||||
|
<view class="device-info" > |
||||
|
<image src="/utils/image/ic_blue_remote.png" class="bluetooth-icon"></image> |
||||
|
<view class="device-details"> |
||||
|
<text class="device-name">{{item.name || '未知设备'}}</text> |
||||
|
<text class="device-mac">MAC: {{item.deviceId}}</text> |
||||
|
</view> |
||||
|
</view> |
||||
|
<view class="device-status"> |
||||
|
<text class="signal-strength" style="margin-left: 30rpx;">{{item.RSSI}}</text> |
||||
|
<image src="/utils/image/rss_line.png" class="signal-icon"></image> |
||||
|
<button class="connect-btn" bindtap="connectDevice" size="mini">连接</button> |
||||
|
</view> |
||||
|
</view> --> |
||||
|
|
||||
|
<view class="device-list"> |
||||
|
<view class="device-item" wx:for="{{deviceList}}" |
||||
|
wx:key="deviceId" |
||||
|
> |
||||
|
<view class="device-info" > |
||||
|
<image src="/utils/image/ic_blue_remote.png" class="bluetooth-icon"></image> |
||||
|
<view class="device-details"> |
||||
|
<text class="device-name">{{item.name || '未知设备'}}</text> |
||||
|
<text class="device-mac">{{item.deviceId}}</text> |
||||
|
</view> |
||||
|
</view> |
||||
|
<view class="device-status"> |
||||
|
<text class="signal-strength" style="margin-left: 30rpx;">{{item.RSSI}}</text> |
||||
|
<image src="/utils/image/rss_line.png" class="signal-icon"></image> |
||||
|
<button class="connect-btn" bindtap="connectDevice" size="mini" bindtap="connectDevice" |
||||
|
data-deviceid="{{item.deviceId}}" |
||||
|
data-blename="{{item.name}}">连接</button> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
@ -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; |
||||
|
} |
@ -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" |
||||
|
} |
@ -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 |
||||
|
} |
||||
|
] |
||||
|
} |
||||
|
} |
||||
|
} |
@ -0,0 +1,7 @@ |
|||||
|
{ |
||||
|
"desc": "关于本文件的更多信息,请参考文档 https://developers.weixin.qq.com/miniprogram/dev/framework/sitemap.html", |
||||
|
"rules": [{ |
||||
|
"action": "allow", |
||||
|
"page": "*" |
||||
|
}] |
||||
|
} |
@ -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, |
||||
|
} |
@ -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是蓝牙设备自己断开返回 */ |
After Width: | Height: | Size: 2.0 KiB |
After Width: | Height: | Size: 3.6 KiB |
@ -0,0 +1,9 @@ |
|||||
|
const logEnable = true |
||||
|
export function log(msg) { |
||||
|
if(logEnable) { |
||||
|
console.log('play : ' + JSON.stringify(msg)) |
||||
|
} |
||||
|
} |
||||
|
export default { |
||||
|
log |
||||
|
} |
@ -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, |
||||
|
}; |
@ -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 |
||||
|
} |
Loading…
Reference in new issue