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