当前位置: 首页 > news >正文

UniApp微信小程序-实现蓝牙功能

前言:UniApp提供了跨平台的蓝牙API,官方文档。最近有个需求,需要搜索并连接到对应的蓝牙设备,然后连接到对应的 wifi 网络,有同样需求的伙伴可以参考下。
本次功能主要使用到了以下几种蓝牙API:
在这里插入图片描述
本次功能涉及到两个页面:【本次ui页面的代码没有放出来,大家可以根据自己的实际需求进行调整】
在这里插入图片描述

一、第一个页面内涉及到的API:

页面初始值:

data() {return {bluetoothDeviceList: [],isShowLoading: false,};
},
onLoad() {this.searchBluetoothDevice()
},
1. 初始化蓝牙设备。uni.openBluetoothAdapter()
// 初始化蓝牙设备
searchBluetoothDevice(){let that = thisuni.openBluetoothAdapter({success(res) {console.log('openBluetoothAdapter success', res)that.startBluetoothDevicesDiscovery()},fail(err) {console.log('openBluetoothAdapter err=', err);if (err.errCode === 10001) {uni.$u.toast('请打开蓝牙')}}})
},
2. 开始搜索附近的蓝牙设备。uni.startBluetoothDevicesDiscovery()

(此操作比较耗费系统资源,请在搜索并连接到设备后调用 uni.stopBluetoothDevicesDiscovery 方法停止搜索。)

// 搜索蓝牙设备
startBluetoothDevicesDiscovery() {let that = this;if (this.isShowLoading) {this.stopBluetoothDevicesDiscovery()return}this.isShowLoading = trueuni.startBluetoothDevicesDiscovery({allowDuplicatesKey: true,// services: ['0000abcd-0000-1000-8000-00805f9bffff'], //传入这个参数,只搜索主服务为该UUID的设备success(res) {console.log('startBluetoothDevicesDiscovery success', res)that.onBluetoothDeviceFound()setTimeout(() => {console.log("----BluetoothDevicesDiscovery finish---- ");if (that.isShowLoading){that.stopBluetoothDevicesDiscovery()}}, 10000);},fail(err) {console.log('startBluetoothDevicesDiscovery err=', err);}})
},
3. 监听已经搜索到的蓝牙设备。uni.onBluetoothDeviceFound()

(并且展示到页面上)

// 监听搜索到的蓝牙设备
onBluetoothDeviceFound() {uni.onBluetoothDeviceFound((res) => {res.devices.forEach(device => {if (!device.name && !device.localName) {return}const idx = this.bluetoothDeviceList.findIndex(d => d.deviceId === device.deviceId)if (idx === -1) {this.bluetoothDeviceList.push(device)} else {this.bluetoothDeviceList[idx] = device}})})
},
4. 停止搜索蓝牙设备。uni.stopBluetoothDevicesDiscovery()

(搜索到之后或者搜索失败,都需要调用停止搜索蓝牙设备api)

// 停止蓝牙设备的搜索
stopBluetoothDevicesDiscovery() {this.isShowLoading = falseuni.stopBluetoothDevicesDiscovery()
},
5. 关闭蓝牙模块。uni.closeBluetoothAdapter()

( 页面关闭时,调用该api )

onUnload() {uni.closeBluetoothAdapter()
},
6. 连接低功耗蓝牙设备。uni.createBLEConnection()
// 点击列表里需要的蓝牙进行连接,蓝牙列表里可以拿到deviceId
createBLEConnection(deviceId){uni.showLoading({mask: true})let that = thisuni.createBLEConnection({// 这里的 deviceId 需要已经通过 createBLEConnection 与对应设备建立链接deviceId,success(res) {uni.hideLoading()uni.$u.toast('连接成功')setTimeout(()=>{uni.$u.route(`/pages3/bluetooth/form?deviceId=${deviceId}`)}, 1000)},fail(err) {uni.hideLoading()console.log('createBLEConnection err:', err)}})
},

二、第二个页面内涉及到的API:

页面初始值:

import { hexToUtf8Text, stringToArrayBuffer } from '@/util/hexUtils.js'
data() {return {deviceId: null,globalServiceuuId: '0000abcd-0000-1000-8000-00805f9b34fb', // 自己设备提供的serviceIdglobalWriteId:  '0000abce-0000-1000-8000-00805f9b34fb', // 自己设备提供的写入需要的characteristicIdglobalNotifyId: '0000abd0-0000-1000-8000-00805f9b34fb', // 自己设备提供的notify需要的characteristicIdismy_service: false,characteristicId: '',serviceId: '',};
},
onLoad({deviceId}) {this.deviceId = deviceIdthis.getBLEDeviceServices(deviceId)
},
1. 获取蓝牙设备所有服务(service)。uni.getBLEDeviceServices()
// 获取serviceId
getBLEDeviceServices(deviceId) {let that = thisuni.getBLEDeviceServices({deviceId,success: (res) => {console.log("service size = ", res.services.length)for (let i = 0; i < res.services.length; i++) {console.log(res.services[i].uuid, 'res.services[i].uuid');if (this.globalServiceuuId.toUpperCase() == res.services[i].uuid){that.getBLEDeviceCharacteristics(deviceId, res.services[i].uuid)}}}})
},
2. 获取蓝牙设备某个服务中所有特征值(characteristic)。uni.getBLEDeviceCharacteristics()

注:因为 2 3 4 涉及到的三个api写到一个了方法里,所以这三个模块的代码粘贴的一样。

// 获取characteristicId
getBLEDeviceCharacteristics(deviceId, serviceId) {uni.getBLEDeviceCharacteristics({deviceId,serviceId,success: (res) => {var ismy_service = falseconsole.log("compute ", serviceId, this.globalServiceuuId.toUpperCase())if (serviceId == this.globalServiceuuId.toUpperCase()) {ismy_service = trueconsole.warn("this is my service ")}console.log('getBLEDeviceCharacteristics success', res.characteristics)for (let i = 0; i < res.characteristics.length; i++) {let item = res.characteristics[i]// 该特征值是否支持 write 操作if (item.properties.write) {if (ismy_service && (this.globalWriteId.toUpperCase() == item.uuid)){console.warn("find write uuid  ready to ", item.uuid)this.characteristicId = item.uuidthis.serviceId = serviceId}}// 该特征值是否支持 notify 操作if (item.properties.notify || item.properties.indicate) {console.log("[Notify]", item.uuid)if (ismy_service && (this.globalNotifyId.toUpperCase() == item.uuid)){uni.notifyBLECharacteristicValueChange({  //开启通知deviceId,serviceId,characteristicId: item.uuid,state: true, success(res) {console.log('notifyBLECharacteristicValueChange success', res)},fail(err) {console.warn("notifyBLECharacteristicValueChange err", err)}})}}}},fail(err) {console.error('getBLEDeviceCharacteristics err', err)}})// 后端返回的数据-进行接收// 操作之前先监听,保证第一时间获取数据uni.onBLECharacteristicValueChange((res) => {uni.showLoading({mask: true})const buffer = res.value;if (buffer.byteLength > 0) {uni.hideLoading()const uint8Arr = new Uint8Array(buffer); // 转成 Uint8Array 查看二进制值const hexStr = Array.from(uint8Arr, byte => byte.toString(16).padStart(2, '0')).join(''); // 转16进制字符串let resData = hexToUtf8Text(hexStr) // 16进制转换成文本console.log(resData, '===resData');if(resData==='A1'){uni.$u.toast('连接成功')}else if(resData==='A0'){uni.$u.toast('连接失败')}else if(resData==='B1'){uni.$u.toast('检测联网成功')}else if(resData==='B0'){uni.$u.toast('检测联网失败')}else if(resData==='B2'){uni.$u.toast('检测联网超时')}else{uni.showToast({title: resData, icon:'none',duration: 3000})}}else{uni.hideLoading()}})
},
3. 启用 notify 功能,订阅特征值。uni.notifyBLECharacteristicValueChange()
// 获取characteristicId
getBLEDeviceCharacteristics(deviceId, serviceId) {uni.getBLEDeviceCharacteristics({deviceId,serviceId,success: (res) => {var ismy_service = falseconsole.log("compute ", serviceId, this.globalServiceuuId.toUpperCase())if (serviceId == this.globalServiceuuId.toUpperCase()) {ismy_service = trueconsole.warn("this is my service ")}console.log('getBLEDeviceCharacteristics success', res.characteristics)for (let i = 0; i < res.characteristics.length; i++) {let item = res.characteristics[i]// 该特征值是否支持 write 操作if (item.properties.write) {if (ismy_service && (this.globalWriteId.toUpperCase() == item.uuid)){console.warn("find write uuid  ready to ", item.uuid)this.characteristicId = item.uuidthis.serviceId = serviceId}}// 该特征值是否支持 notify 操作if (item.properties.notify || item.properties.indicate) {console.log("[Notify]", item.uuid)if (ismy_service && (this.globalNotifyId.toUpperCase() == item.uuid)){uni.notifyBLECharacteristicValueChange({  //开启通知deviceId,serviceId,characteristicId: item.uuid,state: true, success(res) {console.log('notifyBLECharacteristicValueChange success', res)},fail(err) {console.warn("notifyBLECharacteristicValueChange err", err)}})}}}},fail(err) {console.error('getBLEDeviceCharacteristics err', err)}})// 后端返回的数据-进行接收// 操作之前先监听,保证第一时间获取数据uni.onBLECharacteristicValueChange((res) => {uni.showLoading({mask: true})const buffer = res.value;if (buffer.byteLength > 0) {uni.hideLoading()const uint8Arr = new Uint8Array(buffer); // 转成 Uint8Array 查看二进制值const hexStr = Array.from(uint8Arr, byte => byte.toString(16).padStart(2, '0')).join(''); // 转16进制字符串let resData = hexToUtf8Text(hexStr) // 16进制转换成文本console.log(resData, '===resData');if(resData==='A1'){uni.$u.toast('连接成功')}else if(resData==='A0'){uni.$u.toast('连接失败')}else if(resData==='B1'){uni.$u.toast('检测联网成功')}else if(resData==='B0'){uni.$u.toast('检测联网失败')}else if(resData==='B2'){uni.$u.toast('检测联网超时')}else{uni.showToast({title: resData, icon:'none',duration: 3000})}}else{uni.hideLoading()}})
},
4. 监听低功耗蓝牙设备的特征值变化事件。uni.onBLECharacteristicValueChange()
// 获取characteristicId
getBLEDeviceCharacteristics(deviceId, serviceId) {uni.getBLEDeviceCharacteristics({deviceId,serviceId,success: (res) => {var ismy_service = falseconsole.log("compute ", serviceId, this.globalServiceuuId.toUpperCase())if (serviceId == this.globalServiceuuId.toUpperCase()) {ismy_service = trueconsole.warn("this is my service ")}console.log('getBLEDeviceCharacteristics success', res.characteristics)for (let i = 0; i < res.characteristics.length; i++) {let item = res.characteristics[i]// 该特征值是否支持 write 操作if (item.properties.write) {if (ismy_service && (this.globalWriteId.toUpperCase() == item.uuid)){console.warn("find write uuid  ready to ", item.uuid)this.characteristicId = item.uuidthis.serviceId = serviceId}}// 该特征值是否支持 notify 操作if (item.properties.notify || item.properties.indicate) {console.log("[Notify]", item.uuid)if (ismy_service && (this.globalNotifyId.toUpperCase() == item.uuid)){uni.notifyBLECharacteristicValueChange({  //开启通知deviceId,serviceId,characteristicId: item.uuid,state: true, success(res) {console.log('notifyBLECharacteristicValueChange success', res)},fail(err) {console.warn("notifyBLECharacteristicValueChange err", err)}})}}}},fail(err) {console.error('getBLEDeviceCharacteristics err', err)}})// 后端返回的数据-进行接收// 操作之前先监听,保证第一时间获取数据uni.onBLECharacteristicValueChange((res) => {uni.showLoading({mask: true})const buffer = res.value;if (buffer.byteLength > 0) {uni.hideLoading()const uint8Arr = new Uint8Array(buffer); // 转成 Uint8Array 查看二进制值const hexStr = Array.from(uint8Arr, byte => byte.toString(16).padStart(2, '0')).join(''); // 转16进制字符串let resData = hexToUtf8Text(hexStr) // 16进制转换成文本console.log(resData, '===resData'); // 这里是设备端返回的数据,根据情况进行提示if(resData==='A1'){uni.$u.toast('连接成功')}else if(resData==='A0'){uni.$u.toast('连接失败')}else if(resData==='B1'){uni.$u.toast('检测联网成功')}else if(resData==='B0'){uni.$u.toast('检测联网失败')}else if(resData==='B2'){uni.$u.toast('检测联网超时')}else{uni.showToast({title: resData, icon:'none',duration: 3000})}}else{uni.hideLoading()}})
},
5. 向低功耗蓝牙设备特征值中写入二进制数据。uni.writeBLECharacteristicValue()

(这里写入数据成功之后,onBLECharacteristicValueChange这个api里面可以接收到设备端返回的数据,然后进行判断或提示)

// 一键连接
writeBLECharacteristicValue() {uni.showLoading({mask: true})let body = {...this.form,msgTag: "wifi_connect", }const str = JSON.stringify(body)  // 这是我们设备端要求的参数,其他人可根据需求情况定var buffer = stringToArrayBuffer(str)uni.writeBLECharacteristicValue({// 这里的 deviceId 需要在 getBluetoothDevices 或 onBluetoothDeviceFound 接口中获取deviceId: this.deviceId,// 这里的 serviceId 需要在 getBLEDeviceServices 接口中获取serviceId: this.serviceId,// 这里的 characteristicId 需要在 getBLEDeviceCharacteristics 接口中获取characteristicId: this.characteristicId,// 这里的value是ArrayBuffer类型value: buffer,success(res) {// uni.hideLoading()console.log('writeBLECharacteristicValue success', res)},fail(err) {uni.hideLoading()console.log('writeBLECharacteristicValue err', err)uni.$u.toast('连接失败')}})
},
6. 断开与低功耗蓝牙的连接。uni.closeBLEConnection()
onUnload() {if (this.deviceId) {uni.closeBLEConnection({deviceId: this.deviceId})}
},

提示:这里是转换二进制,或者16进制转换成可读文本用到的两个方法,单独封装到了utils里面。

// 1. 先定义 utf8Decode 函数(内部函数,无需导出,但需在调用前声明)
function utf8Decode(byteArray) {let str = '';let i = 0;const len = byteArray.length;while (i < len) {if (byteArray[i] < 0x80) {// 1字节字符(0xxxxxxx)str += String.fromCharCode(byteArray[i]);i++;} else if (byteArray[i] >= 0xC0 && byteArray[i] < 0xE0) {// 2字节字符(110xxxxx 10xxxxxx)if (i + 1 >= len) break;const charCode = ((byteArray[i] & 0x1F) << 6) | (byteArray[i + 1] & 0x3F);str += String.fromCharCode(charCode);i += 2;} else if (byteArray[i] >= 0xE0 && byteArray[i] < 0xF0) {// 3字节字符(1110xxxx 10xxxxxx 10xxxxxx)if (i + 2 >= len) break;const charCode = ((byteArray[i] & 0x0F) << 12) | ((byteArray[i + 1] & 0x3F) << 6) | (byteArray[i + 2] & 0x3F);str += String.fromCharCode(charCode);i += 3;} else if (byteArray[i] >= 0xF0 && byteArray[i] < 0xF8) {// 4字节字符(11110xxx 10xxxxxx 10xxxxxx 10xxxxxx)if (i + 3 >= len) break;let charCode = ((byteArray[i] & 0x07) << 18) | ((byteArray[i + 1] & 0x3F) << 12) | ((byteArray[i + 2] & 0x3F) << 6) | (byteArray[i + 3] & 0x3F);if (charCode > 0xFFFF) {// 处理UTF-16代理对charCode -= 0x10000;str += String.fromCharCode((charCode >> 10) + 0xD800, (charCode & 0x3FF) + 0xDC00);} else {str += String.fromCharCode(charCode);}i += 4;} else {// 无效字节,跳过i++;}}return str;
}// 2. 再定义 hexToUtf8Text 函数(调用 utf8Decode,此时函数已声明)
export function hexToUtf8Text(hexStr) {try {const cleanHex = hexStr.replace(/\s/g, '').toLowerCase();if (!/^[0-9a-f]+$/.test(cleanHex)) {throw new Error('16进制格式错误:仅允许0-9、a-f字符');}if (cleanHex.length % 2 !== 0) {throw new Error('16进制长度错误:需为偶数(每2位对应1个字节)');}// 16进制转字节数组const byteLength = cleanHex.length / 2;const byteArray = new Uint8Array(byteLength);for (let i = 0; i < byteLength; i++) {byteArray[i] = parseInt(cleanHex.substr(i * 2, 2), 16);}// 调用 utf8Decode(此时函数已存在,不会报“未找到”错误)return utf8Decode(byteArray);} catch (error) {console.error('16进制转文本失败:', error);return `转换失败:${error.message}`;}
}// 3. 转成二进制
export function stringToArrayBuffer(str) {var bytes = new Array();var len, c;len = str.length;for (var i = 0; i < len; i++) {c = str.charCodeAt(i);if (c >= 0x010000 && c <= 0x10ffff) {bytes.push(((c >> 18) & 0x07) | 0xf0);bytes.push(((c >> 12) & 0x3f) | 0x80);bytes.push(((c >> 6) & 0x3f) | 0x80);bytes.push((c & 0x3f) | 0x80);} else if (c >= 0x000800 && c <= 0x00ffff) {bytes.push(((c >> 12) & 0x0f) | 0xe0);bytes.push(((c >> 6) & 0x3f) | 0x80);bytes.push((c & 0x3f) | 0x80);} else if (c >= 0x000080 && c <= 0x0007ff) {bytes.push(((c >> 6) & 0x1f) | 0xc0);bytes.push((c & 0x3f) | 0x80);} else {bytes.push(c & 0xff);}}var array = new Int8Array(bytes.length);for (var i in bytes) {array[i] = bytes[i];}return array.buffer;
}

快乐学习!


文章转载自:

http://JPu1p7Yz.yfnjk.cn
http://bVLrEQej.yfnjk.cn
http://agMF7L0l.yfnjk.cn
http://lOGfHMI3.yfnjk.cn
http://CC6eJtpP.yfnjk.cn
http://xSINz14N.yfnjk.cn
http://lxLU5dg2.yfnjk.cn
http://9eFrfKPL.yfnjk.cn
http://8SdkE1zU.yfnjk.cn
http://yyxc241b.yfnjk.cn
http://hejOk5aH.yfnjk.cn
http://CxBkG1oD.yfnjk.cn
http://x9bVxct2.yfnjk.cn
http://ocJhp2ip.yfnjk.cn
http://v0cgzA7j.yfnjk.cn
http://HwaLeaZ4.yfnjk.cn
http://LxVIKYbo.yfnjk.cn
http://a9ZG6SfV.yfnjk.cn
http://IKPQL6nt.yfnjk.cn
http://JK1qw3F3.yfnjk.cn
http://pkYf1VId.yfnjk.cn
http://Tw4VyOGO.yfnjk.cn
http://o0PWesbx.yfnjk.cn
http://mITgdwBa.yfnjk.cn
http://QyfvUAEl.yfnjk.cn
http://FShIr4e1.yfnjk.cn
http://KAyTafWm.yfnjk.cn
http://CRZEZFmW.yfnjk.cn
http://oNQ3Mwtk.yfnjk.cn
http://RZRAhXpE.yfnjk.cn
http://www.dtcms.com/a/377571.html

相关文章:

  • Java集成SmartJavaAI实现旋转框检测、定向边界框目标检测(YOLO-OBB)
  • FreeBSD系统使用freebsd-update命令从14.2升级到14.3
  • 【Java】Hibernate查询性能优化
  • Spring DI/IOC核心原理详解
  • 基于多时间尺度的电动汽车光伏充电站联合分层优化调度(Matlab代码实现)
  • 【论文阅读】TrojVLM: Backdoor Attack Against Vision Language Models
  • 快速查看文件的MD5码
  • 多模态大模型研究每日简报【2025-09-10】
  • 股指期货合约的代码如何理解?
  • 基于Python的商品爬取与可视化系统
  • SEGGER_RTT相关的操作
  • vmware虚拟机 ubuntu固定usb转rndis网卡
  • Java管理事务方式
  • Spring Boot + Vue 项目中使用 Redis 分布式锁案例
  • Unity(①基础)
  • 【测量】知识点
  • 开始 ComfyUI 的 AI 绘图之旅-ControlNet(六)
  • 楼宇自控系统监控建筑变配电系统:功效体现在安全与节能层面
  • 分布式存储:RustFS与MinIO全面对比
  • 【第24话:定位建图】 SLAM回环检测方法及原理详细介绍
  • Electron 核心模块速查表
  • SafeEar:浙大和清华联合推出的AI音频伪造检测框架,错误率低至2.02%
  • vue2+jessibuca播放h265视频
  • 智普科技推出 Claude 用户平滑迁移方案,GLM-4.5 模型全面开放
  • IIS 部署 asp.net core 项目时,出现500.19、500.31问题的解决方案
  • ASP.NET Core 中的简单授权
  • 可遇不可求的自动化运维工具 | 2 | 实施阶段一:基础准备
  • Golang安装笔记
  • 【记录】Docker|Docker内部访问LInux主机上的Ollama服务
  • MySQL 日期时间类型:从入门到精通的核心指南