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

news2024/11/18 23:25:59

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


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

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

我看了很多网上的文章,也鼓捣了很长时间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 = 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进制
          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 = 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: [],
    });
    //断开连接的时候清理图表数据
    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进制
          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.coloradmin.cn/o/1716098.html

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

相关文章

uni-app全局弹窗的实现方案

背景 为了解决uni-app 任意位置出现弹窗 解决方案 一、最初方案 受限于uni-app 调用组件需要每个页面都引入注册才可以使用&#xff0c;此方案繁琐&#xff0c;每个页面都要写侵入性比较强 二、改进方案 app端&#xff1a;新建一个页面进行跳转&#xff0c;可以实现伪弹窗…

探索数组处理:奇数的筛选与替换

新书上架~&#x1f447;全国包邮奥~ python实用小工具开发教程http://pythontoolsteach.com/3 欢迎关注我&#x1f446;&#xff0c;收藏下次不迷路┗|&#xff40;O′|┛ 嗷~~ 目录 一、数组中的奇数筛选 二、将奇数替换为负一 总结 一、数组中的奇数筛选 在处理数组数据时…

在WHM中如何调整max_post_size参数大小

今日我们在搭建新网站时需要调整一下PHP参数max_post_size 的大小&#xff0c;我们公司使用的Hostease的美国独立服务器产品默认5个IP地址&#xff0c;也购买了cPanel面板&#xff0c;因此联系Hostease的技术支持&#xff0c;寻求帮助了解到如何在WHM中调整PHP参数&#xff0c;…

告别虚拟机,在Windows10启动Linux子系统

背景 如果要在自己的windows电脑安装一个Linux系统,一般是用虚拟机软件例如VMware软件来创建。但是这种方式显得非常的笨重。而Windows10自带的Linux子系统则非常的方便。 分析 在Windows10中启用子系统的方式来安装Linux,用于学习和开发是非常方便的。子系统的实用就和一个…

Android SDK下载安装(_指定版本)

安装完sdk&#xff0c;就可以直接使用adb命令了&#xff0c;如果想做app相关自动化测试&#xff0c;也是需要sdk环境依赖的 一、SDK下载 A&#xff1a;官网下载&#xff1a; 管内镜像网站(推荐)&#xff1a;https://www.androiddevtools.cn/index.html 官网&#xff1a;htt…

分享10个我常逛的技术社区

多逛社区&#xff0c;了解新鲜的事情和技术&#xff0c;或许会有意想不到的观点给你灵感&#xff01; 国外技术交流网站合集&#xff08;30个类别&#xff09;的github地址: https://github.com/sdmg15/Best-websites-a-programmer-should-visit 这里收集了超过200个程序员应该…

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…

轻量级 C Logger

目录 一、描述 二、实现效果 三、使用案例 四、内存检测 一、描述 最近实现一个 WS 服务器&#xff0c;内部需要一个日志打印记录服务器程序的运行过程&#xff0c;故自己实现了一个轻量级的 logger&#xff0c;主要包含如下特征&#xff1a; 可输出 debug、info、warn、er…

C++ 进阶(3)虚函数表解析

个人主页&#xff1a;仍有未知等待探索-CSDN博客 专题分栏&#xff1a;C 请多多指教&#xff01; 目录 一、虚函数表 二、单继承&#xff08;无虚函数覆盖&#xff09; 继承关系表&#xff1a; 对于实例&#xff1a;derive d 的虚函数表&#xff1a; 对于实例&#xff1a;b…

现货白银交易点差是多少

现货白银投资者通过交易平台进行买卖操作的时候&#xff0c;平台会以“点差”的形式向投资者收取一定的交易费用。所谓的点差&#xff0c;也就是平台所报出的买入价和卖出价之间的固定差额&#xff0c;由于现货白银的报价是“成对”的&#xff0c;所以点差的存在也是其交易模式…

连锁收银系统支持带结算功能

连锁实体店的收银系统需要支持结算功能&#xff0c;以适应连锁运营效率和提升连锁管理的水平。商淘云连锁收银系统与您一起分享连锁收银系统需支持结算功能的三大必要点。大家点赞收藏&#xff0c;以免划走后找不到了。 一是&#xff0c;连锁模式的运营比较复杂&#xff0c;有加…

【小技巧】Keil C51 报错“*** ERROR L107: ADDRESS SPACE OVERFLOW****

软件&#xff1a;Keil C51 C51V961版本 电脑&#xff1a;Win10 报错提示&#xff1a; compiling System.c... linking... *** ERROR L107: ADDRESS SPACE OVERFLOW SPACE: DATA SEGMENT: ?DT?LCD LENGTH: 0034H Program Size: data174.0 xdata17 code1205 Target not create…

开源进销存系统

推荐一款开源的进销存系统 项目地址&#xff1a;进销存系统 仓库管理系统 SAAS进销存 进销存ERP: 进销存系统 仓库管理系统 SAAS进销存 进销存ERPhttps://gitee.com/flyemu/jxc.git 主要功能模块 销售 采购 库存 资料 设置 支持saas多租户&#xff0c;100%开源可二开 …

【Redis】 关于 Redis 有序集合类型

文章目录 &#x1f343;前言&#x1f334;普通命令介绍&#x1f6a9;zadd&#x1f6a9;zcard&#x1f6a9;zcount&#x1f6a9;zrange&#x1f6a9;zrevrange&#x1f6a9;zrangebyscore&#x1f6a9;zpopmax&#x1f6a9;zpopmin&#x1f6a9;zrank&#x1f6a9;zrevrank&…

办公自动化-Python如何提取Word标题并保存到Excel中?

办公自动化-Python如何提取Word标题并保存到Excel中&#xff1f; 应用场景需求分析实现思路实现过程安装依赖库打开需求文件获取word中所有标题去除不需要的标题创建工作簿和工作表分割标题功能名称存入测试对象GN-TC需求标识符存入测试项标识存入需求标识符 完整源码实现效果学…

交换机的三层交换技术

现有pc1与pc2不在同一个网段之下&#xff0c;通过交换机相连接。 进人交换机1&#xff0c;创建两个vlan 10和vlan 20 &#xff0c;进入串口2设置串口模式为access&#xff0c;并且设置默认vlan为10.进入串口3设置串口模式为access&#xff0c;并且设置默认vlan为20. 进入串口1…

学习笔记——动态路由协议——OSPF(OSPF基本术语)

OSPF基本术语 1、链路状态(LS)与链路状态通告(LSA) 链路(LINK)&#xff1a;路由器上的一个接口。 状态(State)&#xff1a;描述接口以及其与邻居路由器之间的关系。 (1)链路状态(LS) OSPF是一种链路状态协议&#xff0c;所谓的链路状态&#xff0c;其实就是路由器的接口状态…

Java-数组内存解析

文章目录 1.内存的主要结构&#xff1a;栈、堆2.一维数组的内存解析3.二维数组的内存解析 1.内存的主要结构&#xff1a;栈、堆 2.一维数组的内存解析 举例1&#xff1a;基本使用 举例2&#xff1a;两个变量指向一个数组 3.二维数组的内存解析 举例1&#xff1a; 举例2&am…

Thingsboard规则链:Calculate Delta节点详解

在物联网(IoT)应用中&#xff0c;对设备数据的实时分析和处理是优化运营、预测维护的关键。Thingsboard作为一款功能强大的物联网平台&#xff0c;其规则引擎提供了丰富的节点来处理和分析数据流。其中&#xff0c;Calculate Delta节点是一个重要的工具&#xff0c;用于计算连续…

HAL库+LWIP+LAN8720+热插拔

定时任务中&#xff0c;查询LAN8720的状态寄存器 PHY_BSR 0x01&#xff0c;成功读取后&#xff0c;检查16位数据的BIT2&#xff0c;即可获取网线连接状态 uint32_t phyreg 0;if(HAL_ETH_ReadPHYRegister(&g_eth_handler, PHY_BSR, &phyreg) HAL_OK){if(((phyreg >…