关于微信小程序低功耗蓝牙ECharts实时刷新(涉及自定义缓冲区)

简单的蓝牙显示(串口手动发数据测试)


最近搞了这方面的东西,是刚刚开始接触微信小程序,因为是刚刚开始接触蓝牙设备,所以这篇文章适合既不熟悉小程序,又不熟悉蓝牙的新手看。

项目要求是获取到蓝牙传输过来的数据,并显示成图表实时显示;

我看了很多网上的文章,也鼓捣了很长时间ChatGPT,最后弄出来了,其中出现了很多坑,在这里分享一下;

我想了一下这个文章还是写在CSDN,我知道这个平台有点拉,广告多,但毕竟这个平台普及度高,我希望这篇文章能帮到更多的人,至于什么关注看文章,收费!收NMLGB的费!

首先,微信开发者工具一些简单的配置我就不多说了,先说一些坑的地方;

当我刚刚准备在微信小程序搞蓝牙的是时候,当然是先去翻微信的官方文档,官方文档还是很不错的,给了一个蓝牙项目例子,运行起来很丝滑;

蓝牙 (Bluetooth) | 微信开放文档

在这个文档的最后会有一个代码示例:

我下载下来直接用,可以获取到数据,这个示例里提供了一个16进制显示的函数;

其中获取数据的地方是这里:

getBLEDeviceCharacteristics(deviceId, serviceId) {wx.getBLEDeviceCharacteristics({deviceId,serviceId,success: (res) => {console.log('getBLEDeviceCharacteristics success', res.characteristics)for (let i = 0; i < res.characteristics.length; i++) {let item = res.characteristics[i]if (item.properties.read) {wx.readBLECharacteristicValue({deviceId,serviceId,characteristicId: item.uuid,})}if (item.properties.write) {this.setData({canWrite: true})this._deviceId = deviceIdthis._serviceId = serviceIdthis._characteristicId = item.uuidthis.writeBLECharacteristicValue()}if (item.properties.notify || item.properties.indicate) {wx.notifyBLECharacteristicValueChange({deviceId,serviceId,characteristicId: item.uuid,state: true,})}}},fail(res) {console.error('getBLEDeviceCharacteristics', res)}})// 操作之前先监听,保证第一时间获取数据wx.onBLECharacteristicValueChange((characteristic) => {const idx = inArray(this.data.chs, 'uuid', characteristic.characteristicId)const data = {}if (idx === -1) {data[`chs[${this.data.chs.length}]`] = {uuid: characteristic.characteristicId,// value: ab2hex(characteristic.value)  //转16进制value: toASCII(characteristic.value)}} else {data[`chs[${idx}]`] = {uuid: characteristic.characteristicId,value: toASCII(characteristic.value)}}this.setData(data);// 图表刷新this.Refresh2(dataGenerator(this.data.bleDataList01, this.data.chs[0].value, 50));})},
})

有点长,监听数据变化并获取数据的仅仅是 wx.onBLECharacteristicValueChange((characteristic)这部分;

其中这里的data对象获取到的值是这样一个东西:

它会将每个特征值和其绑定的对象值在这里组成一个单个对象。每次获取到蓝牙对象的时候在这里刷新,所以我只要将数据获取并刷新图表的函数卸载这里就行了,也不用写什么定时刷新。

我发现一个问题,这个示例代码跑真机测试经常经常经常连不上,连的我怀疑人生,这样我Log大法输出的变量和任何报错都看不到!

最后还是新建了一个项目,使用这个示例程序的代码重新写才稳定的连接到手机,诸位如果和我一样,那我们真是难兄难弟……

OK,现在回到重构好的之后的程序。

硬件的蓝牙芯片是沁恒,型号是CH9141,这个是他们官网的下载资源:

搜索 CH9141 - 南京沁恒微电子股份有限公司 (wch.cn)

他们提供了Android的串口助手,在电脑端我用的Windows商店的串口工具:

感觉确实比网上的野鸡串口工具好用。

我的的硬件是低功耗蓝牙,这里连接后会有很多uuid,甚至这些uuid还分组,这是用Android串口工具看到的数据:

左边的uuid才是我要的。

我也不是搞硬件的,只能摸索代码,最后发现是这里:

  getBLEDeviceServices(deviceId) {wx.getBLEDeviceServices({deviceId,success: (res) => {console.log(res);//这里的services[1]就是定位分组的位置this.getBLEDeviceCharacteristics(deviceId, res.services[1].uuid)return}})},

这里我的低功耗蓝牙硬件是有多个服务,然后对应的,我要的服务在这里数组中的位置是1:

可以看一下和上面对的上,然后在这个服务里又有两个uuid,我只要第一个(具体服务具体对应),所以我在第一个代码块那里才会有 this.data.chs[0].value这种写法;

所以对接蓝牙数据的时候各位先打印一下这里的services,然后改成自己需要的uuid。

Refresh2(dataLists) {const chart = this.chart;if (chart) {chart.setOption({series: [{data: dataLists}]});console.log('完成刷新');}},

最开始我按照ECharts官网的小程序代码,把ECharts的初始化代码写在了Page对象外面,这样出现了一个问题,我在Page外部不能使用this来吧我声明好的chart对象保存到Page中;

所以改造了一下写法:

Page({data: {motto: 'Hello World',devices: [],connected: false,chs: [],bleDataList01: [],bleDataList02: [],ec: {onInit: null,},option: option,},onLoad() {this.chart = null; // 保存图表实例this.setData({ec: {onInit: this.initChart}});},initChart(canvas, width, height, dpr) {this.chart = echarts.init(canvas, null, {width: width,height: height,devicePixelRatio: dpr // 像素比});canvas.setChart(this.chart);this.chart.setOption(this.data.option);return this.chart;},
})

option是定义在Page外部的ECharts样式变量,所有代码都是写在index.js文件里的,

这样就能保证我在Page内部写ECharts初始化函数,又不用ECharts懒加载了;

最后写一下数据刷新函数,其调用是这样的:

// 图表数据填充
const dataGenerator = (dataList, data, xLength) => {if (data != "") {dataList.push(Number(data));}if (dataList.length === xLength) {dataList.shift()}return dataList;
};//这里的数据刷新是写在Page内部的
Refresh(dataLists) {const chart = this.chart;if (chart) {chart.setOption({series: [{data: dataLists}]});console.log('完成刷新');}},// 图表刷新,在    wx.onBLECharacteristicValueChange中调用,因为是写在Page内部的,所以前面带过this
this.Refresh(dataGenerator(this.data.bleDataList01, this.data.chs[0].value, 50));

整体代码是这样的:

import * as echarts from '../components/ec-canvas/echarts';var option = {title: {text: '蓝牙对接数据图表',left: 'center'},legend: {data: ['测试数据'],top: 50,left: 'center',z: 100},grid: {containLabel: true},tooltip: {show: true,trigger: 'axis'},xAxis: {type: 'category',boundaryGap: true,},yAxis: {x: 'center',type: 'value',},series: [{name: '测试数据',type: 'line',smooth: true,data: []}, ]
};function inArray(arr, key, val) {for (let i = 0; i < arr.length; i++) {if (arr[i][key] === val) {return i;}}return -1;
}// ArrayBuffer转16进度字符串示例
function ab2hex(buffer) {var hexArr = Array.prototype.map.call(new Uint8Array(buffer),function (bit) {return ('00' + bit.toString(16)).slice(-2)})return hexArr.join('');
}function toASCII(buffer) {return String.fromCharCode.apply(null, new Uint8Array(buffer));
};
// 图表数据填充
const dataGenerator = (dataList, data, xLength) => {if (data != "") {dataList.push(Number(data));}if (dataList.length === xLength) {dataList.shift()}return dataList;
};Page({data: {motto: 'Hello World',devices: [],connected: false,chs: [],bleDataList01: [],bleDataList02: [],ec: {onInit: null,},option: option,},onLoad() {this.chart = null; // 保存图表实例this.setData({ec: {onInit: this.initChart}});},Refresh(dataLists) {const chart = this.chart;if (chart) {chart.setOption({series: [{data: dataLists}]});console.log('完成刷新');}},initChart(canvas, width, height, dpr) {this.chart = echarts.init(canvas, null, {width: width,height: height,devicePixelRatio: dpr // 像素比});canvas.setChart(this.chart);this.chart.setOption(this.data.option);return this.chart;},openBluetoothAdapter() {wx.openBluetoothAdapter({success: (res) => {console.log('openBluetoothAdapter success', res)this.startBluetoothDevicesDiscovery()},fail: (res) => {if (res.errCode === 10001) {wx.onBluetoothAdapterStateChange(function (res) {console.log('onBluetoothAdapterStateChange', res)if (res.available) {this.startBluetoothDevicesDiscovery()}})}}})},getBluetoothAdapterState() {wx.getBluetoothAdapterState({success: (res) => {console.log('getBluetoothAdapterState', res)if (res.discovering) {this.onBluetoothDeviceFound()} else if (res.available) {this.startBluetoothDevicesDiscovery()}}})},startBluetoothDevicesDiscovery() {if (this._discoveryStarted) {return}this._discoveryStarted = truewx.startBluetoothDevicesDiscovery({allowDuplicatesKey: true,success: (res) => {console.log('startBluetoothDevicesDiscovery success', res)this.onBluetoothDeviceFound()},})},stopBluetoothDevicesDiscovery() {wx.stopBluetoothDevicesDiscovery()},onBluetoothDeviceFound() {wx.onBluetoothDeviceFound((res) => {res.devices.forEach(device => {if (!device.name && !device.localName) {return}const foundDevices = this.data.devicesconst idx = inArray(foundDevices, 'deviceId', device.deviceId)const data = {}if (idx === -1) {data[`devices[${foundDevices.length}]`] = device} else {data[`devices[${idx}]`] = device}this.setData(data)})})},createBLEConnection(e) {const ds = e.currentTarget.datasetconst deviceId = ds.deviceIdconst name = ds.namewx.createBLEConnection({deviceId,success: (res) => {this.setData({connected: true,name,deviceId,})this.getBLEDeviceServices(deviceId)}})this.stopBluetoothDevicesDiscovery()},closeBLEConnection() {wx.closeBLEConnection({deviceId: this.data.deviceId})this.setData({connected: false,chs: [],canWrite: false,bleDataList01: [],bleDataList02: [],});//断开连接的时候清理图表数据if (this.chart) {this.chart.setOption({series: [{data: []}]});}console.log('Bluetooth connection closed and data cleared');},getBLEDeviceServices(deviceId) {wx.getBLEDeviceServices({deviceId,success: (res) => {console.log(res);//这里的services[1]就是定位分组的位置this.getBLEDeviceCharacteristics(deviceId, res.services[1].uuid)return}})},getBLEDeviceCharacteristics(deviceId, serviceId) {wx.getBLEDeviceCharacteristics({deviceId,serviceId,success: (res) => {console.log('getBLEDeviceCharacteristics success', res.characteristics)for (let i = 0; i < res.characteristics.length; i++) {let item = res.characteristics[i]if (item.properties.read) {wx.readBLECharacteristicValue({deviceId,serviceId,characteristicId: item.uuid,})}if (item.properties.write) {this.setData({canWrite: true})this._deviceId = deviceIdthis._serviceId = serviceIdthis._characteristicId = item.uuidthis.writeBLECharacteristicValue()}if (item.properties.notify || item.properties.indicate) {wx.notifyBLECharacteristicValueChange({deviceId,serviceId,characteristicId: item.uuid,state: true,})}}},fail(res) {console.error('getBLEDeviceCharacteristics', res)}})// 操作之前先监听,保证第一时间获取数据wx.onBLECharacteristicValueChange((characteristic) => {const idx = inArray(this.data.chs, 'uuid', characteristic.characteristicId)const data = {}if (idx === -1) {data[`chs[${this.data.chs.length}]`] = {uuid: characteristic.characteristicId,// value: ab2hex(characteristic.value)  //转16进制value: toASCII(characteristic.value)}} else {data[`chs[${idx}]`] = {uuid: characteristic.characteristicId,value: toASCII(characteristic.value)}}this.setData(data);// 图表刷新this.Refresh(dataGenerator(this.data.bleDataList01, this.data.chs[0].value, 50));})},writeBLECharacteristicValue() {// 向蓝牙设备发送一个0x00的16进制数据let buffer = new ArrayBuffer(1)let dataView = new DataView(buffer)dataView.setUint8(0, Math.random() * 255 | 0)wx.writeBLECharacteristicValue({deviceId: this._deviceId,serviceId: this._deviceId,characteristicId: this._characteristicId,value: buffer,})},closeBluetoothAdapter() {wx.closeBluetoothAdapter()this._discoveryStarted = false},
})

再说一下,这些代码都是写在index.js文件里的,

这个是项目代码:GitHub - DingAi/WeChatProject-EchartsBluetooth: 一个微信小程序,用Echarts实时显示蓝牙数据

蓝牙数据缓冲区

以上是一些蓝牙的渐渐调试,在次之后我又对接了真实蓝牙设备,新的问题出现了,发过来的串并没有什么中断规律,虽然是能连在一块,但是从串的哪里间断完全不知道,例如:

FE01A1455E34FFFFFF,这个是我的16进制数据,FE01开头,FFFFFF结尾,中间是数据,它发过来可能是完整的,也可能是55E34FFFFFFFE01E136,也可能是FF0,也可能的FFFFFF……

所以只能写个缓冲区,然后冲缓冲区里识别数据帧取数据,这里用的是字符串对象来做缓冲区:
 

extractFirstFrame(buffer) {// 找到第一个 "fe01" 的位置let startIndex = buffer.indexOf("fe01");// 如果找不到 "fe01",直接返回空字符串if (startIndex === -1) {return "";}// 从 "fe01" 开始查找 "ffffff"let endIndex = buffer.indexOf("ffffff", startIndex);// 如果找不到 "ffffff",直接返回空字符串if (endIndex === -1) {return "";}// 提取完整的数据帧let frame = buffer.substring(startIndex, endIndex + 6);// 更新缓冲区,移除已处理的部分buffer = buffer.substring(endIndex + 6);return {frame,buffer};},

这里的缓冲区处理其实很简单,诸位可以考虑将其改成批量处理的形式,我也写了一点会在后面的代码中贴出来,但是我还没有写完批量处理,诸位也就看看。

缓冲区对象我直接在Page的data中定义了:
 

Page({data: {devices: [],connected: false,chs: [],bleDataList01: [],bleDataList02: [],ec: {onInit: null,},option: option,Buffer: "",display: [],},

在这里说一下,因为是字符串缓冲区,所以一定要注意十六进制的大小写问题,我就在这里卡了一下

缓冲区的逻辑是将数据存链接到Buffer字符串后面,逐渐累加,然后由程序判断取出一帧数据,将其中的数据取出来翻译成数字(这里的翻译和硬件人员对接一下)
取出来最前面的数据帧之后,把数据帧分析一下,我这里数据帧里包含了4个数据:

function hexStringToFloats(hexStr) {// if (hexStr.length !== 16) {//   throw new Error("Hex string must be exactly 16 characters long");// }// 拆分成四个部分,每部分两个字节(16 位)const intParts = [hexStr.substring(4, 8),hexStr.substring(8, 12),hexStr.substring(12, 16),hexStr.substring(16, 20),];// 将每部分的 16 进制字符串转换成整数const intArray = intParts.map(part => parseInt(part, 16));// 将每个整数除以 100,还原成浮点数const floatArray = intArray.map(intValue => intValue / 100);return floatArray;
}

最后干脆直接将数据处理整合到一个函数:
 

handleFrames() {const {frame,buffer} = this.extractFirstFrame(this.data.Buffer); // 提取帧数据并更新缓冲区const value = hexStringToFloats(frame); // 从帧数据中提取后两位数据并转换为浮点数const [dataList1, dataList2] = this.dataGenerator(this.data.bleDataList01, this.data.bleDataList02, value, 20); // 使用dataGenerator处理数据,最后一个数值是this.setData({Buffer: buffer}); // 更新缓冲区this.Refresh([dataList1, dataList2]); // 更新图表// console.log(this.extractFrames(this.data.Buffer));},

整函数在前面说的获取到data并且可以做数据刷新的地方调用一下:
 

// 操作之前先监听,保证第一时间获取数据wx.onBLECharacteristicValueChange((characteristic) => {const idx = inArray(this.data.chs,"uuid",characteristic.characteristicId);const data = {};if (idx === -1) {data[`chs[${this.data.chs.length}]`] = {uuid: characteristic.characteristicId,value: ab2hex(characteristic.value), //转16进制};// this.setData({//   Buffer: this.data.Buffer + ab2hex(characteristic.value)// });} else {data[`chs[${idx}]`] = {uuid: characteristic.characteristicId,value: ab2hex(characteristic.value),};this.setData({Buffer: this.data.Buffer + ab2hex(characteristic.value)});}console.log(data);this.setData(data);// 从缓冲区提取帧并处理this.handleFrames();});},

最后说下,硬件的数据发送速度不能太快了,要不然处理速度可能跟不上。

我发现自己写的程序并不能在公布GItHub上之后下载给别人用,和微信账号有绑定,我也是才接触微信小程序,不了解,各位就在这里看下代码(有一些函数没有使用)吧:

import * as echarts from "../components/ec-canvas/echarts";var option = {// title: {//   text: "蓝牙对接数据图表",//   left: "center",// },legend: {data: ["红外", "红"],top: 20,left: "center",z: 100,},grid: {containLabel: true,},tooltip: {show: true,trigger: "axis",},xAxis: {type: "category",boundaryGap: true,},yAxis: {x: "center",type: "value",min: function (value) {return value.min;}},series: [{name: "红外",type: "line",showSymbol: false, // 取消小圆点显示data: [],},{name: "红",type: "line",showSymbol: false, // 取消小圆点显示data: [],},],dataZoom: [{type: 'slider', // 滑动条型数据区域缩放组件  yAxisIndex: [0], // 控制 Y 轴,这里假设有两个 Y 轴  start: 0, // 左边在 10% 的位置  end: 100 // 右边在 60% 的位置  },{type: 'inside', // 内置型数据区域缩放组件  yAxisIndex: [0],start: 0,end: 100}]
};function inArray(arr, key, val) {for (let i = 0; i < arr.length; i++) {if (arr[i][key] === val) {return i;}}return -1;
}// ArrayBuffer转16进制字符串
function ab2hex(buffer) {var hexArr = Array.prototype.map.call(new Uint8Array(buffer), function (bit) {return ("00" + bit.toString(16)).slice(-2);});return hexArr.join("");
}// 从提取的帧数据中获取后两位数据并返回
function hexStringToFloats(hexStr) {// if (hexStr.length !== 16) {//   throw new Error("Hex string must be exactly 16 characters long");// }// 拆分成四个部分,每部分两个字节(16 位)const intParts = [hexStr.substring(4, 8),hexStr.substring(8, 12),hexStr.substring(12, 16),hexStr.substring(16, 20),];// 将每部分的 16 进制字符串转换成整数const intArray = intParts.map(part => parseInt(part, 16));// 将每个整数除以 100,还原成浮点数const floatArray = intArray.map(intValue => intValue / 100);return floatArray;
}Page({data: {devices: [],connected: false,chs: [],bleDataList01: [],bleDataList02: [],ec: {onInit: null,},option: option,Buffer: "",display: [],},onLoad() {this.chart = null; // 保存图表实例this.setData({ec: {onInit: this.initChart,},});},// 从缓冲区中提取完整帧数据并更新缓冲区extractFrames(buffer, maxFrames = 100) {const frames = [];let startIndex = 0;let endIndex;// 循环直到找到足够的帧或buffer中没有更多帧  while (frames.length < maxFrames && startIndex !== -1) {// 找到下一个"fe01"的位置  startIndex = buffer.indexOf("fe01", startIndex);// 如果找不到"fe01",跳出循环  if (startIndex === -1) break;// 从当前"fe01"开始查找"ffffff"  endIndex = buffer.indexOf("ffffff", startIndex);// 如果找不到"ffffff",则尝试下一个"fe01"  if (endIndex === -1) {startIndex++; // 或者你可以选择跳过一定的字节数来避免无限循环  continue;}// 提取完整的数据帧  let frame = buffer.substring(startIndex, endIndex + 6);frames.push(frame); // 将帧添加到frames数组中  // 更新startIndex为下一个可能的"fe01"位置  startIndex = endIndex + 6;}// 更新缓冲区,移除已处理的部分  buffer = buffer.substring(startIndex);return frames;},extractFirstFrame(buffer) {// 找到第一个 "fe01" 的位置let startIndex = buffer.indexOf("fe01");// 如果找不到 "fe01",直接返回空字符串if (startIndex === -1) {return "";}// 从 "fe01" 开始查找 "ffffff"let endIndex = buffer.indexOf("ffffff", startIndex);// 如果找不到 "ffffff",直接返回空字符串if (endIndex === -1) {return "";}// 提取完整的数据帧let frame = buffer.substring(startIndex, endIndex + 6);// 更新缓冲区,移除已处理的部分buffer = buffer.substring(endIndex + 6);return {frame,buffer};},// 将数据添加到图表数据列表dataGenerator(dataList1, dataList2, valueList, xLength) {// if (valueList.length > 0) {dataList1.push(valueList[0]);dataList2.push(valueList[1]);// }this.setData({display: "心跳: " + valueList[2] + " 血氧: " + valueList[3]})if (dataList1.length === xLength) {dataList1.shift();dataList2.shift();}return [dataList1, dataList2];},Refresh(dataList) {const chart = this.chart;if (chart) {chart.setOption({series: [{data: dataList[0],},{data: dataList[1],},],});}},handleFrames() {const {frame,buffer} = this.extractFirstFrame(this.data.Buffer); // 提取帧数据并更新缓冲区const value = hexStringToFloats(frame); // 从帧数据中提取后两位数据并转换为浮点数const [dataList1, dataList2] = this.dataGenerator(this.data.bleDataList01, this.data.bleDataList02, value, 20); // 使用dataGenerator处理数据,最后一个数值是this.setData({Buffer: buffer}); // 更新缓冲区this.Refresh([dataList1, dataList2]); // 更新图表// console.log(this.extractFrames(this.data.Buffer));},initChart(canvas, width, height, dpr) {this.chart = echarts.init(canvas, null, {width: width,height: height,devicePixelRatio: dpr, // 像素比});canvas.setChart(this.chart);this.chart.setOption(this.data.option);return this.chart;},openBluetoothAdapter() {wx.openBluetoothAdapter({success: (res) => {console.log("openBluetoothAdapter success", res);this.startBluetoothDevicesDiscovery();},fail: (res) => {if (res.errCode === 10001) {wx.onBluetoothAdapterStateChange(function (res) {console.log("onBluetoothAdapterStateChange", res);if (res.available) {this.startBluetoothDevicesDiscovery();}});}},});},getBluetoothAdapterState() {wx.getBluetoothAdapterState({success: (res) => {console.log("getBluetoothAdapterState", res);if (res.discovering) {this.onBluetoothDeviceFound();} else if (res.available) {this.startBluetoothDevicesDiscovery();}},});},startBluetoothDevicesDiscovery() {if (this._discoveryStarted) {return;}this._discoveryStarted = true;wx.startBluetoothDevicesDiscovery({allowDuplicatesKey: true,success: (res) => {console.log("startBluetoothDevicesDiscovery success", res);this.onBluetoothDeviceFound();},});},stopBluetoothDevicesDiscovery() {wx.stopBluetoothDevicesDiscovery();},onBluetoothDeviceFound() {wx.onBluetoothDeviceFound((res) => {res.devices.forEach((device) => {if (!device.name && !device.localName) {return;}const foundDevices = this.data.devices;const idx = inArray(foundDevices, "deviceId", device.deviceId);const data = {};if (idx === -1) {data[`devices[${foundDevices.length}]`] = device;} else {data[`devices[${idx}]`] = device;}this.setData(data);});});},createBLEConnection(e) {const ds = e.currentTarget.dataset;const deviceId = ds.deviceId;const name = ds.name;wx.createBLEConnection({deviceId,success: (res) => {this.setData({connected: true,name,deviceId,});this.getBLEDeviceServices(deviceId);},});this.stopBluetoothDevicesDiscovery();},closeBLEConnection() {wx.closeBLEConnection({deviceId: this.data.deviceId,});this.setData({connected: false,chs: [],canWrite: false,bleDataList01: [],bleDataList02: [],buffer: "",});//断开连接的时候清理图表数据if (this.chart) {this.chart.setOption({series: [{data: [],}, ],});}console.log("Bluetooth connection closed and data cleared");},getBLEDeviceServices(deviceId) {wx.getBLEDeviceServices({deviceId,success: (res) => {console.log(res);//这里的services[1]就是定位分组的位置this.getBLEDeviceCharacteristics(deviceId, res.services[1].uuid);return;},});},getBLEDeviceCharacteristics(deviceId, serviceId) {wx.getBLEDeviceCharacteristics({deviceId,serviceId,success: (res) => {console.log("getBLEDeviceCharacteristics success", res.characteristics);for (let i = 0; i < res.characteristics.length; i++) {let item = res.characteristics[i];if (item.properties.read) {wx.readBLECharacteristicValue({deviceId,serviceId,characteristicId: item.uuid,});}if (item.properties.write) {this.setData({canWrite: true,});this._deviceId = deviceId;this._serviceId = serviceId;this._characteristicId = item.uuid;this.writeBLECharacteristicValue();}if (item.properties.notify || item.properties.indicate) {wx.notifyBLECharacteristicValueChange({deviceId,serviceId,characteristicId: item.uuid,state: true,});}}},fail(res) {console.error("getBLEDeviceCharacteristics", res);},});// 操作之前先监听,保证第一时间获取数据wx.onBLECharacteristicValueChange((characteristic) => {const idx = inArray(this.data.chs,"uuid",characteristic.characteristicId);const data = {};if (idx === -1) {data[`chs[${this.data.chs.length}]`] = {uuid: characteristic.characteristicId,value: ab2hex(characteristic.value), //转16进制};// this.setData({//   Buffer: this.data.Buffer + ab2hex(characteristic.value)// });} else {data[`chs[${idx}]`] = {uuid: characteristic.characteristicId,value: ab2hex(characteristic.value),};this.setData({Buffer: this.data.Buffer + ab2hex(characteristic.value)});}console.log(data);this.setData(data);// 从缓冲区提取帧并处理this.handleFrames();});},writeBLECharacteristicValue() {// 向蓝牙设备发送一个0x00的16进制数据let buffer = new ArrayBuffer(1);let dataView = new DataView(buffer);dataView.setUint8(0, (Math.random() * 255) | 0);wx.writeBLECharacteristicValue({deviceId: this._deviceId,serviceId: this._deviceId,characteristicId: this._characteristicId,value: buffer,});},closeBluetoothAdapter() {wx.closeBluetoothAdapter();this._discoveryStarted = false;},
});

对于前端的的wxml基本没啥变化,就有一个数值显示的绑定,在这里就不贴了,我这里数据显示还是正常的:


 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/19105.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

在线思维导图编辑!3个AI思维导图生成软件推荐!

思维导图&#xff0c;一种以创新为驱动的视觉化思考工具&#xff0c;已经渗透到我们日常生活和工作的各个角落。当我们需要整理思绪、规划项目或者梳理信息时&#xff0c;思维导图总能提供极大的帮助。 近些年随着云服务等基础设施的完善&#xff0c;我们可以看到越来越多提供…

ITIL4认证考试这么贵,还值得考证吗,有必要学吗?

从2023年4月1日开始&#xff0c;ITIL 4是Foundation认证将会捆绑OTM(Official Training Materials),这样在一次ITIL4的考试费中将会捆绑&#xff1a;试卷费电子教材书费监考费OTM费&#xff0c;每一种考试费都相较于2022年有涨幅&#xff0c;再加上PeopleCert收取的授权机构的授…

视频监控业务平台LntonCVS国标GB28181视频平台智慧城市应用方案

随着科技的不断进步&#xff0c;尤其是人工智能技术的飞速发展&#xff0c;视频应用已经超越了传统的视频监控、视频会议、视频通话和视频指挥调度等基本功能。它们正在向更加多元化、灵活化、融合化和智能化的方向发展。因此&#xff0c;建立一个视频AI中台变得至关重要。 通过…

持续领跑教育科技,网易有道再发“子曰”教育大模型全新应用

5月29日&#xff0c;网易有道“子曰”教育大模型媒体交流会在北京举行。会上&#xff0c;网易有道分享了子曰教育大模型最新技术进展及三大AI创新应用&#xff1a;AI全科学习助手“有道小P”APP、新一代虚拟人口语教练Hi Echo 3.0和新一代知识库问答引擎QAnything。 现场&…

基于STM32单片机老人体温心率血氧跌倒定位短信报警

一.硬件及设计功能 以STM32F103C8T6为中央处理器&#xff0c;GPS模块用采集数据&#xff0c;将数据发送给单片机后&#xff0c;单片机根据定位计算公式得出当前位置的经纬度信息和时间信息。经过LCD显示器处理后得出和时间信息SIM800模块发送短信到设定的手机号上&#xff0c;将…

17 C语言学生管理系统

学生管理系统 &#x1f44d;&#x1f602;&#x1f4af; 项目代码 代码可能存在细节上的错误&#xff0c;希望大家可以指导意见。 #define _CRT_SECURE_NO_WARNINGS 1#include <stdio.h> #include <stdlib.h> #include <string.h>#define MAX_STUDENTS 100…

conda修改环境名称后,无法安装包,显示no such file

1问题描述 原本创建环境时设置的名字不太合适&#xff0c;但是因为重新创建环境很麻烦&#xff0c;安装很多包。。所以想直接对包名进行修改&#xff0c;本人采用的方式是直接找到conda环境的文件目录&#xff0c;然后修改文件名&#xff0c;简单粗暴。确实修改成功了&#xf…

模拟集成电路(5)----单级放大器(共栅级)

模拟集成电路(5)----单级放大器&#xff08;共栅级&#xff09; 有一些场合需要一些小的输入电阻&#xff08;电流放大器&#xff09; 大信号分析 − W h e n V i n ≥ V B − V T H ∙ M 1 i s o f f , V o u t V D D − F o r L o w e r V i n I d 1 2 μ n C o x W L ( V…

手摸手教你uniapp原生插件开发

行有余力,心无恐惧 这篇技术文章写了得有两三个礼拜,虽然最近各种事情,工作上的生活上的,但是感觉还是有很多时间被浪费.还记得几年前曾经有一段时间7点多起床运动,然后工作学习,看书提升认知.现在我都要佩服那会儿的自己.如果想回到那种状态,我觉得需要有三个重要的条件. 其…

xcode依赖包package已经安装,但是提示No such module ‘Alamofire‘解决办法

明明已经通过xcode自带的swift包管理器安装好了依赖包&#xff0c;但是却还是提示&#xff1a;No such module&#xff0c;这个坑爹的xcode&#xff0c;我也只能说服气&#xff0c;但是无奈&#xff0c;没办法攻打苹果总部&#xff0c;只能自己想解决办法了 No such module Ala…

香港优才计划找中介是否是智商税,靠谱中介又该如何找?

关于香港优才计划的申请&#xff0c;找中介帮助还是自己DIY&#xff0c;网络上充斥的声音太多&#xff0c;对不了解的人来说&#xff0c;难以抉择的同时还怕上当受骗。 这其中很容易误导人的关键在于——信息差&#xff01; 今天这篇文章的目的就是想让大家看清一些中介和DIY…

MoE模型大火,源2.0-M32诠释“三个臭皮匠,顶个诸葛亮”!

文 | 智能相对论 作者 | 陈泊丞 近半年来&#xff0c;MoE混合专家大模型彻底是火了。 在海外&#xff0c;OpenAI的GPT-4、谷歌的Gemini、Mistral AI的Mistral、xAI的Grok-1等主流大模型都采用了MoE架构。而在国内&#xff0c;浪潮信息也刚刚发布了基于MoE架构的“源2.0-M3…

C++【缺省参数|函数重载|引用】

目录 1 缺省参数 1.1 全缺省 1.2 半缺省 注意 1.3 应用 2 函数重载 函数重载的概念 1、参数类型不同 2、参数个数不同 3、参数类型顺序不同 3 引用 3.1 引用概念 3.2 引用特性 3.3 常引用 3.4 使用场景 3.5 传值、传引用效率比较 3.6 引用和指针的区别 1 缺…

基于51单片机的温度+烟雾报警系统设计

一.硬件方案 本设计采用51单片机为核心控制器&#xff0c;利用气体传感器MQ-2、ADC0832模数转换器、DS18B20温度传感器等实现基本功能。通过这些传感器和芯片&#xff0c;当环境中可燃气体浓度或温度等发生变化时系统会发出相应的灯光报警信号和声音报警信号&#xff0c;以此来…

【C++课程学习】:二叉树的基本函数实现

&#x1f381;个人主页&#xff1a;我们的五年 &#x1f50d;系列专栏&#xff1a;C课程学习 &#x1f389;欢迎大家点赞&#x1f44d;评论&#x1f4dd;收藏⭐文章 目录 &#x1f349;二叉树的结构类型&#xff1a; &#x1f349;1.创建二叉树函数&#xff08;根据数组&am…

30【Aseprite 作图】桌子——拆解

1 桌子只要画左上方&#xff0c;竖着5&#xff0c;斜着3个1&#xff0c;斜着两个2&#xff0c;斜着2个3&#xff0c;斜着一个5&#xff0c;斜着一个很长的 然后左右翻转 再上下翻转 在桌子腿部分&#xff0c;竖着三个直线&#xff0c;左右都是斜线&#xff1b;这是横着水平线不…

Python os.path.isfile() 和 os.path.isdir() 函数

Python os.path.isfile 和 os.path.isdir 函数 正文 正文 在网上看到很多人对这两个函数的用法有过说明&#xff0c;然而感觉都没有说到它们的本质&#xff0c;这里特来记录一下。os.path.isfile() 用来判断所给参数是否一个文件。os.path.isdir() 用来判断所给的参数是否是一…

Mybatis多表查询

MyBatis-多表查询-一对一查询(方式一) 一个菜品对应一个分类 直接菜品记录category对象 菜品id写入Dish,后面的分类直接写入 Category类 封装,如果sql不能封装上,那么直接使用resultmap封装 使用resultType只能封装基本属性 所以要定义一个resultmap手动封装 使用标签 要…

Python数据处理,使用 tkinter 模块点击获取文件目录

Python数据处理&#xff0c;使用 tkinter 模块点击获取文件目录 正文 正文 当我们进行数据处理读取文件内数据的时候&#xff0c;通常&#xff0c;我们需要设定好一个存放当前文件所在目录的变量。比如如下目录&#xff1a; file_path rC:\Users\xxx\Desktop\DataSet\Data.c…

【车载开发系列】Vector工具链的安装

【车载开发系列】Vector工具链的安装 【车载开发系列】Vector工具链的安装 【车载开发系列】Vector工具链的安装一. VectorDriver二. DaVinci_Developer三. DaVinci Configurator 一. VectorDriver Vector Driver Setup是Vector产品链中重要的驱动软件,所有的硬件设备进行连接…