最近搞了这方面的东西,是刚刚开始接触微信小程序,因为是刚刚开始接触蓝牙设备,所以这篇文章适合既不熟悉小程序,又不熟悉蓝牙的新手看。
项目要求是获取到蓝牙传输过来的数据,并显示成图表实时显示;
我看了很多网上的文章,也鼓捣了很长时间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)这部分;
我发现一个问题,这个示例代码跑真机测试经常经常经常连不上,连的我怀疑人生,这样我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实时显示蓝牙数据