Browse Source

Initial Commit

master
facaige888 5 months ago
commit
010d7e29b2
  1. 14
      .gitignore
  2. 28
      app.js
  3. 29
      app.json
  4. 10
      app.wxss
  5. 74
      pages/datalist/brand.js
  6. 6
      pages/datalist/brand.json
  7. 27
      pages/datalist/brand.wxml
  8. 13
      pages/datalist/brand.wxss
  9. 416
      pages/detail/detail.js
  10. 6
      pages/detail/detail.json
  11. 3
      pages/detail/detail.wxml
  12. 4
      pages/detail/detail.wxss
  13. 172
      pages/index/index.js
  14. 5
      pages/index/index.json
  15. 41
      pages/index/index.wxml
  16. 33
      pages/index/index.wxss
  17. 18
      pages/logs/logs.js
  18. 4
      pages/logs/logs.json
  19. 6
      pages/logs/logs.wxml
  20. 16
      pages/logs/logs.wxss
  21. 554
      pages/newdetail/newdetail.js
  22. 6
      pages/newdetail/newdetail.json
  23. 9
      pages/newdetail/newdetail.wxml
  24. 19
      pages/newdetail/newdetail.wxss
  25. 56
      pages/newzk/newlist.js
  26. 6
      pages/newzk/newlist.json
  27. 11
      pages/newzk/newlist.wxml
  28. 8
      pages/newzk/newlist.wxss
  29. 57
      pages/playvideo/video.js
  30. 6
      pages/playvideo/video.json
  31. 16
      pages/playvideo/video.wxml
  32. 22
      pages/playvideo/video.wxss
  33. 158
      pages/service/service.js
  34. 5
      pages/service/service.json
  35. 41
      pages/service/service.wxml
  36. 81
      pages/service/service.wxss
  37. 29
      project.config.json
  38. 48
      project.private.config.json
  39. 7
      sitemap.json
  40. 582
      utils/bleUtil.js
  41. 6
      utils/constants.js
  42. BIN
      utils/image/ic_blue_remote.png
  43. BIN
      utils/image/rss_line.png
  44. 9
      utils/log.js
  45. 0
      utils/stringUtils.js
  46. 69
      utils/timeUtils.js
  47. 19
      utils/util.js

14
.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/

28
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
}
})

29
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"
}

10
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;
}

74
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'
});
},
})

6
pages/datalist/brand.json

@ -0,0 +1,6 @@
{
"usingComponents": {
},
"navigationBarTitleText": "OBD分类"
}

27
pages/datalist/brand.wxml

@ -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>

13
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;
}

416
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);
}
});
});
}
});
}
})

6
pages/detail/detail.json

@ -0,0 +1,6 @@
{
"usingComponents": {
},
"navigationBarTitleText": "详情"
}

3
pages/detail/detail.wxml

@ -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>

4
pages/detail/detail.wxss

@ -0,0 +1,4 @@
.list-item {
padding: 10px;
border-bottom: 1px solid #ccc;
}

172
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)
});
}
})

5
pages/index/index.json

@ -0,0 +1,5 @@
{
"usingComponents": {
},
"navigationBarTitleText": "CAR匹配"
}

41
pages/index/index.wxml

@ -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>

33
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;
}

18
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
}
})
})
}
})

4
pages/logs/logs.json

@ -0,0 +1,4 @@
{
"usingComponents": {
}
}

6
pages/logs/logs.wxml

@ -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>

16
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);
}

554
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.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);
}
});
});
}
});
}
})

6
pages/newdetail/newdetail.json

@ -0,0 +1,6 @@
{
"usingComponents": {
},
"navigationBarTitleText": "OBD分类"
}

9
pages/newdetail/newdetail.wxml

@ -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>

19
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;
}

56
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'
});
},
})

6
pages/newzk/newlist.json

@ -0,0 +1,6 @@
{
"usingComponents": {
},
"navigationBarTitleText": "OBD分类"
}

11
pages/newzk/newlist.wxml

@ -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>

8
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;
}

57
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'
});
}
});

6
pages/playvideo/video.json

@ -0,0 +1,6 @@
{
"usingComponents": {
},
"navigationBarTitleText": "视频教程"
}

16
pages/playvideo/video.wxml

@ -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>

22
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;
}

158
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)
});
}
})

5
pages/service/service.json

@ -0,0 +1,5 @@
{
"usingComponents": {
},
"navigationBarTitleText": "CAR匹配"
}

41
pages/service/service.wxml

@ -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>

81
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;
}

29
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"
}

48
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
}
]
}
}
}

7
sitemap.json

@ -0,0 +1,7 @@
{
"desc": "关于本文件的更多信息,请参考文档 https://developers.weixin.qq.com/miniprogram/dev/framework/sitemap.html",
"rules": [{
"action": "allow",
"page": "*"
}]
}

582
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,
}

6
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是蓝牙设备自己断开返回 */

BIN
utils/image/ic_blue_remote.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

BIN
utils/image/rss_line.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

9
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
}

0
utils/stringUtils.js

69
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,
};

19
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
}
Loading…
Cancel
Save