Vue3 + UniApp 蓝牙连接与数据发送(稳定版)

      本教程适用于使用 uni-app + Vue3 (script setup) 开发的跨平台 App(支持微信小程序、H5、Android/iOS 等)

🎯 功能目标

  • ✅ 获取蓝牙权限
  • ✅ 扫描周围蓝牙设备
  • ✅ 连接指定蓝牙设备
  • ✅ 获取服务和特征值
  • ✅ 向设备发送数据包(ArrayBuffer)
  • ✅ 页面 UI 展示设备列表 + 操作按钮

项目结构概览

/pages/bluetooth/
├── index.vue         # 主页面(本教程重点)
└── utils/Common.ts   # 公共方法(获取系统信息等)

 其中的公共方法代码:

export async function getSystemInfo() {return await uni.getSystemInfo();
}

第一步:申请蓝牙权限并初始化蓝牙适配器

onShow() 生命周期中检查并申请蓝牙权限:

import { onShow } from "@dcloudio/uni-app";
import { ref } from "vue";let btOpenStatus = ref<boolean>(false);
let devicesList = ref<UniApp.BluetoothDeviceInfo[]>([]);onShow(() => {uni.authorize({scope: 'scope.bluetooth',success() {console.log('蓝牙权限已授权');initBluetooth();},fail() {showToast('请开启蓝牙权限!');}});
});

初始化蓝牙模块

function initBluetooth() {uni.onBluetoothAdapterStateChange(function (res) {btOpenStatus.value = res.available;if (res.available) startBluetoothScan(); // 蓝牙打开后开始扫描});uni.openBluetoothAdapter({success: () => {startBluetoothScan();},fail: (err) => {if (err.errCode == 10001) {btOpenStatus.value = false;showToast('蓝牙未打开!');}}});
}

🔍 第二步:扫描蓝牙设备

function startBluetoothScan() {uni.startBluetoothDevicesDiscovery({success: (res) => {console.log("开始扫描蓝牙设备...", res);},fail: (err) => {console.error("启动扫描失败", err);showToast("启动蓝牙扫描失败");}});uni.onBluetoothDeviceFound((res) => {res.devices.forEach((device) => {const exists = devicesList.value.some(d => d.deviceId === device.deviceId);if (!exists) devicesList.value.push(device);});});
}

🔗 第三步:连接蓝牙设备

const connectedDevice = ref({serviceOrFeature: [] as Array<{ service: any, characteristics?: any }>,devicesInfo: {} as UniApp.BluetoothDeviceInfo
});async function createBLEConnection(device: UniApp.BluetoothDeviceInfo) {uni.showToast({duration: 30000,icon: "loading",title: '蓝牙正在连接中!'});uni.createBLEConnection({deviceId: device.deviceId,success(connectionRes) {if (connectionRes.errCode === 0) {showToast('蓝牙连接成功');connectedDevice.value.devicesInfo = device;getBLEDeviceServices(device.deviceId).then(res => {if (res.code === 200) console.log('蓝牙服务初始化完成');});}},fail(connectionRes) {if (connectionRes.errCode === 10000) {showToast('请检查蓝牙是否开启!');} else if (connectionRes.errCode === 10010 || connectionRes.errCode === -1) {console.log('已经连接');}},complete() {uni.hideToast();}});
}

⚙️ 第四步:获取服务与特征值

function getBLEDeviceServices(deviceId: string): Promise<{ code: number }> {return new Promise(ok => {uni.getBLEDeviceServices({deviceId,success: (res) => {res.services.forEach(async (item) => {let characteristicsRes = await getBLEDeviceCharacteristics(deviceId, item.uuid);if (characteristicsRes.code === 200) {connectedDevice.value.serviceOrFeature.push({service: item,characteristics: characteristicsRes.data});ok({ code: 200 });}});},fail: (err) => {ok({ code: 201 });}});});
}function getBLEDeviceCharacteristics(deviceId: string, serviceId: string): Promise<{ code: number, data?: any }> {return new Promise(ok => {uni.getBLEDeviceCharacteristics({deviceId,serviceId,success: (res) => {ok({ code: 200, data: res.characteristics });},fail: () => {ok({ code: 201 });}});});
}

💬 第五步:向蓝牙设备发送数据

function getBluetoothServiceFeature(propertyName: string): { serviceUUID: string, feature: any } {let result = { serviceUUID: '', feature: {} };connectedDevice.value.serviceOrFeature.forEach(item => {let found = item.characteristics.find(f => f.properties[propertyName]);if (found) {result.serviceUUID = item.service.uuid;result.feature = found;}});return result;
}function sendMsg(msg: any, isBuffer?: boolean) {let writeFeature = getBluetoothServiceFeature('write');if (!writeFeature) {console.log('蓝牙没有对应的写服务权限!');return;}uni.writeBLECharacteristicValue({deviceId: connectedDevice.value.devicesInfo.deviceId,serviceId: writeFeature.serviceUUID,characteristicId: writeFeature.feature.uuid,value: isBuffer ? msg : stringToArrayBuffer(msg),success(res) {console.log('消息发送成功', res);},fail(res) {console.log('消息发送失败', res);}});
}function stringToArrayBuffer(str: string): ArrayBuffer {const buffer = new ArrayBuffer(str.length);const view = new Uint8Array(buffer);for (let i = 0; i < str.length; i++) {view[i] = str.charCodeAt(i);}return buffer;
}

完整代码

<template><template><scroll-view scroll-y style="height: 100vh;background: #f9f9f9;" class="device-list"><!-- 设备列表 --><view v-for="device in devicesList" :key="device.deviceId" class="device-card"><!-- 设备信息 --><view class="device-info"><text class="name">{{ device.name || '未知设备' }}</text><text class="id">ID: {{ device.deviceId }}</text></view><!-- 操作按钮 --><view class="actions"><text class="btn connect" @click.stop="createBLEConnection(device)">连接</text><text class="btn send" @click.stop="sendMsg('测试发送信息')">发送信息</text></view></view><!-- 空状态提示 --><view v-if="devicesList.length === 0" class="empty-state">正在搜索附近的蓝牙设备...</view></scroll-view></template>
</template><script setup lang="ts">import { onShow } from "@dcloudio/uni-app";import { ref , watch } from "vue";import { getSystemInfo } from "@/utils/Common";let systemInfo = ref();	let btOpenStatus = ref<boolean>();let devicesList = ref<UniApp.BluetoothDeviceInfo[]>([]); // 用于存储搜索到的设备onShow( async () => {systemInfo.value = await getSystemInfo();uni.authorize({scope: 'scope.bluetooth',success() {console.log('蓝牙权限已授权');initBluetooth();},fail() {showToast('请开启蓝牙权限!');}});});function initBluetooth() {uni.onBluetoothAdapterStateChange(function (res) {console.log(`蓝牙状态变化,用户${res.available ? '打开' : '关闭'}蓝牙!`);btOpenStatus.value = res.available;if(res.available) {startBluetoothScan();}});uni.openBluetoothAdapter({success: () => {console.log("蓝牙适配器已打开!");startBluetoothScan(); // 开始扫描设备},fail: (err) => {if (err.errCode == 10001) {btOpenStatus.value = false;showToast('蓝牙未打开!');}}});}function startBluetoothScan() {uni.startBluetoothDevicesDiscovery({success: (res) => {console.log("开始扫描蓝牙设备...",res);},fail: (err) => {console.error("启动扫描失败", err);showToast("启动蓝牙扫描失败");}});// 监听新发现的设备uni.onBluetoothDeviceFound((res) => {// 遍历发现的设备res.devices.forEach((device) => {// 去重:根据 deviceId 判断是否已存在const exists = devicesList.value.some(d => d.deviceId === device.deviceId);if (!exists) {devicesList.value.push(device);}});});}const connectedDevice = ref({serviceOrFeature: [] as Array<{ service: any, characteristics ? : any }>,devicesInfo: {} as UniApp.BluetoothDeviceInfo});/*** 连接蓝牙设备*/async function createBLEConnection(device: UniApp.BluetoothDeviceInfo) {await uni.getLocation({});if(devicesList.value.length <= 0) {showToast('正在搜索附近的蓝牙设备');return;}uni.showToast({duration: 30000,icon: "loading",title: '蓝牙正在连接中!'});console.log('选择的蓝牙设备:',device);if(device) {connectedDevice.value.devicesInfo = device;uni.createBLEConnection({deviceId: device.deviceId,async success(connectionRes) {if(connectionRes.errCode == 0) {console.log('连接成功!');showToast('蓝牙连接成功');let servicesRes = await getBLEDeviceServices(device.deviceId);if(servicesRes.code == 200) {console.log('蓝牙初始化服务完成');}}},fail(connectionRes) {if(connectionRes.errCode == 10000) {showToast('请检查蓝牙是否开启!');}else if(connectionRes.errCode == 10000) {showToast('蓝牙连接失败,可以重试!');}else if(connectionRes.errCode == 10010 || connectionRes.errCode == -1) {console.log('已经连接');}},complete() {uni.hideToast();}});}}/*** 获取蓝牙设备的服务(service)*/function getBLEDeviceServices(deviceId: string) : Promise<{code : number}> {return new Promise( ok => {uni.getBLEDeviceServices({deviceId,success: (res) => {res.services.forEach(async (item) => {let characteristicsRes = await getBLEDeviceCharacteristics(deviceId,item.uuid);if(characteristicsRes.code == 200) {connectedDevice.value.serviceOrFeature.push({service: item,characteristics: characteristicsRes.data});ok({ code : 200 });}});},fail: (err) => {console.log("获取服务失败", err);ok({ code : 201 });}});});}/*** 获取蓝牙设备的特征值(characteristic)*/async function getBLEDeviceCharacteristics(deviceId: string, serviceId: string) : Promise<{ code : number , data ? : any }> {return new Promise( ok => {uni.getBLEDeviceCharacteristics({deviceId,serviceId,success: (res) => {ok({code: 200,data: res.characteristics});},fail: () => {ok({code : 201})}});});}/*** 获取连接设备的写特征值(wirteCharacteristic)*/function getBluetoothServiceFeature(propertyName: string): { serviceUUID: string, feature: any } {let serviceFeatureInfo: { serviceUUID: string, feature: any } = { serviceUUID: '', feature: {} };connectedDevice.value.serviceOrFeature.forEach(item => {let foundFeature = item.characteristics.find((feature: any) => feature.properties[propertyName]);if (foundFeature) {serviceFeatureInfo.serviceUUID = item.service.uuid;serviceFeatureInfo.feature = foundFeature;return;}});return serviceFeatureInfo;}// 向蓝牙写数据function sendMsg(msg: any, isBuffer ? : boolean ) {console.log('发送的信息:',msg);	let writeServiceFeature = getBluetoothServiceFeature('write');if (!writeServiceFeature) {console.log('蓝牙没有对应的写服务权限!');return;}uni.writeBLECharacteristicValue({deviceId: connectedDevice.value.devicesInfo.deviceId,serviceId: writeServiceFeature.serviceUUID,characteristicId: writeServiceFeature.feature.uuid, value: isBuffer ? msg : stringToArrayBuffer(msg) as any,writeType: systemInfo.value.osName == 'ios' ? 'write' : 'writeNoResponse',success(res) {console.log('消息发送成功', res);},fail(res) {console.log('消息发送失败', res);}});}function stringToArrayBuffer(str: string): ArrayBuffer {const buffer = new ArrayBuffer(str.length);const view = new Uint8Array(buffer);for (let i = 0; i < str.length; i++) {view[i] = str.charCodeAt(i);}return buffer;}function showToast(title: string) {uni.showToast({icon: 'none',title});}</script><style lang="scss" scoped>.device-card {background-color: #fff;border-radius: 8px;padding: 16px;margin-bottom: 12px;box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);display: flex;flex-direction: column;gap: 10px;}.device-info {.name {font-weight: bold;font-size: 16px;color: #333;}.id {font-size: 14px;color: #888;display: block;margin-top: 4px;}}.actions {display: flex;gap: 10px;.btn {flex: 1;text-align: center;padding: 8px 0;border-radius: 4px;font-size: 14px;}.connect {color: #fff;background-color: #6659E5;}.send {color: #fff;background-color: #FC5531;}}.empty-state {text-align: center;padding: 20px;color: #999;}
</style>

🛠️ 补充建议

功能实现方式
显示 RSSI 信号强度在设备项中显示 {{ device.RSSI }} dBm
自动刷新设备列表使用定时器每隔几秒重新扫描
防止重复点击连接添加 connectingDeviceId 状态控制
发送自定义数据包使用 buildBluetoothPacket() 构造特定格式数据

📦 最终效果预览

前端UI部分


📌 总结

✅ 本教程实现了从蓝牙权限申请 → 设备扫描 → 连接设备 → 获取服务 → 特征值读写 → 数据发送的一整套流程。

🎯 适用于智能门锁、手环、打印机、IoT 等需要蓝牙通信的场景。

💡 如果你需要对接具体蓝牙协议(如 BLE 服务 UUID、数据格式),欢迎继续提问,我可以帮你定制!

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

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

相关文章

Docker + Nginx + Logrotate 日志管理与轮换实践

概述与背景 Docker 容器化环境中 Nginx 日志管理的挑战Logrotate 的作用与必要性结合场景的实际需求&#xff08;如日志切割、压缩、归档&#xff09; Docker 环境下的 Nginx 日志配置 Nginx 日志路径与 Docker 数据卷映射 volumes:- ./nginx/logs:/var/log/nginxLogrotate …

涂胶协作机器人解决方案 | Kinova Link 6 Cobot在涂胶工业的方案应用与价值

涂胶工业现状背景&#xff1a; 涂胶工艺在汽车制造、电子组装、航空航天等工业领域极为关键&#xff0c;关乎产品密封、防水、绝缘性能及外观质量。 然而&#xff0c;传统涂胶作业问题频发。人工操作重复性强易疲劳&#xff0c;涂胶质量波动大&#xff1b;大型涂胶器使用增加工…

释放模型潜力:浅谈目标检测微调技术(Fine-tuning)

引言 在计算机视觉领域&#xff0c;目标检测是一项至关重要的任务&#xff0c;它不仅要识别出图像中存在哪些物体&#xff0c;还要精确地定位它们的位置。从自动驾驶汽车识别行人与车辆&#xff0c;到医疗影像辅助诊断病灶&#xff0c;再到智能安防监控异常事件&#xff0c;目标…

Unreal从入门到精通之 UE4 vs UE5 VR性能优化实战

文章目录 前言:准备工作UE4 vs UE5 性能对比引擎核心技术方案对比UE5 优化总结项目设置可伸缩性组设置VolumetricCloud最后前言: 最近在使用UE5制作VR项目 制作完后发现,我们的场景一直很卡顿,场景优化也做到了极致,但是帧率最高也才30+ 但是我们看到一个竞品,他的帧率竟…

爆炸仿真的学习日志

今天学习了一下【Workbench LS-DYNA中炸药在空气中爆炸的案例-哔哩哔哩】 https://b23.tv/kmXlN29 一开始 如果你的 ANSYS Workbench 工具箱&#xff08;Toolbox&#xff09;里 只有 SPEOS&#xff0c;即使尝试了 右键刷新、重置视图、显示全部 等方法仍然没有其他分析系统&a…

Redis部署架构详解:原理、场景与最佳实践

文章目录 Redis部署架构详解&#xff1a;原理、场景与最佳实践单点部署架构原理适用场景优势劣势最佳实践 主从复制架构原理消息同步机制1. 全量同步&#xff08;Full Resynchronization&#xff09;2. 部分重同步&#xff08;Partial Resynchronization&#xff09;3. 心跳检测…

AI预测3D新模型百十个定位预测+胆码预测+去和尾2025年6月6日第100弹

从今天开始&#xff0c;咱们还是暂时基于旧的模型进行预测&#xff0c;好了&#xff0c;废话不多说&#xff0c;按照老办法&#xff0c;重点8-9码定位&#xff0c;配合三胆下1或下2&#xff0c;杀1-2个和尾&#xff0c;再杀4-5个和值&#xff0c;可以做到100-300注左右。 (1)定…

验证电机理论与性能:电机试验平板提升测试效率

电机试验平板提升测试效率是验证电机理论与性能的重要环节之一。通过在平板上进行电机试验&#xff0c;可以对电机的性能参数进行准确测量和分析&#xff0c;从而验证电机的理论设计是否符合实际表现。同时&#xff0c;提升测试效率可以加快试验过程&#xff0c;节约时间和成本…

C语言 — 编译和链接

目录 1.程序从源文件到结果输出的执行过程2.预处理3.编译3.1 词法分析3.2 语法分析3.3 语义分析3.4 生成test.s文件 4.汇编5.链接6.运行 1.程序从源文件到结果输出的执行过程 2.预处理 预处理阶段的执行操作&#xff1a; 预处理阶段会将#define定义的常量或宏进行替换&#x…

传统业务对接AI-AI编程框架-Rasa的业务应用实战(5)--Rasa成型可用 rasa服务化部署及识别意图后的决策及行为

此篇接续上一篇 传统业务对接AI-AI编程框架-Rasa的业务应用实战&#xff08;4&#xff09;--Rasa成型可用 针对业务配置rasa并训练和部署 上一篇我们已经让Rasa准确识别了我们自然语言指令的开票和查询发票的意图和实体。 # 开具发票场景 用户输入&#xff1a;开具一张1000元…

MajicTryOn(基于wanvideo的虚拟试穿项目)

网络结构 Attention模块详解 左边服装通过qwen2.5-VL-7B来生成详细的服装描述&#xff1b;线条提取器产生相应的线条map&#xff1b;garment和line map通过vae转换为潜在空间特征&#xff0c;然后分别经过patchfier,最后通过zero proj得到Garment Tokens和Line Tokens;右边是di…

JAVA-什么是JDK?

1.JDK 的定义 JDK&#xff08;Java Development Kit&#xff09;是 Java 开发工具包&#xff0c;是 Oracle 官方提供的用于开发、编译和运行 Java 应用程序的核心工具集。它包含了编写 Java 程序所需的编译器、调试工具、库文件以及运行时环境&#xff08;JRE&#xff09;。 2…

Palo Alto Networks Expedition存在命令注入漏洞(CVE-2025-0107)

免责声明 本文档所述漏洞详情及复现方法仅限用于合法授权的安全研究和学术教育用途。任何个人或组织不得利用本文内容从事未经许可的渗透测试、网络攻击或其他违法行为。使用者应确保其行为符合相关法律法规,并取得目标系统的明确授权。 对于因不当使用本文信息而造成的任何直…

分布式光纤传感(DAS)技术应用解析:从原理到落地场景

近年来&#xff0c;分布式光纤传感&#xff08;Distributed Acoustic Sensing&#xff0c;DAS&#xff09;技术正悄然改变着众多传统行业的感知方式。它将普通的通信光缆转化为一个长距离、连续分布的“听觉传感器”&#xff0c;对振动、声音等信号实现高精度、高灵敏度的监测。…

独家首发!低照度环境下YOLOv8的增强方案——从理论到TensorRT部署

文章目录 引言一、低照度图像增强技术现状1.1 传统低照度增强方法局限性1.2 深度学习-based方法进展 二、Retinexformer网络原理2.1 Retinex理论回顾2.2 Retinexformer创新架构2.2.1 光照感知Transformer2.2.2 多尺度Retinex分解2.2.3 自适应特征融合 三、YOLOv8-Retinexformer…

96. 2017年蓝桥杯省赛 - Excel地址(困难)- 进制转换

96. Excel地址&#xff08;进制转换&#xff09; 1. 2017年蓝桥杯省赛 - Excel地址&#xff08;困难&#xff09; 标签&#xff1a;2017 省赛 1.1 题目描述 Excel 单元格的地址表示很有趣&#xff0c;它使用字母来表示列号。 比如&#xff0c; A 表示第 1 列&#xff0c;…

EtherNet/IP转DeviceNet协议网关详解

一&#xff0c;设备主要功能 疆鸿智能JH-DVN-EIP本产品是自主研发的一款EtherNet/IP从站功能的通讯网关。该产品主要功能是连接DeviceNet总线和EtherNet/IP网络&#xff0c;本网关连接到EtherNet/IP总线中做为从站使用&#xff0c;连接到DeviceNet总线中做为从站使用。 在自动…

Druid连接池实现自定义数据库密码加解密功能详解

Druid连接池实现自定义数据库密码加解密功能详解 在企业级应用开发中&#xff0c;数据库密码的明文存储是一个显著的安全隐患。Druid作为阿里巴巴开源的高性能数据库连接池组件&#xff0c;提供了灵活的密码加密与解密功能&#xff0c;允许开发者通过自定义逻辑实现数据库密码…

生成 Git SSH 证书

&#x1f511; 1. ​​生成 SSH 密钥对​​ 在终端&#xff08;Windows 使用 Git Bash&#xff0c;Mac/Linux 使用 Terminal&#xff09;执行命令&#xff1a; ssh-keygen -t rsa -b 4096 -C "your_emailexample.com" ​​参数说明​​&#xff1a; -t rsa&#x…

Java并发编程实战 Day 12:阻塞队列与线程协作

【Java并发编程实战 Day 12】阻塞队列与线程协作 开篇 欢迎来到“Java并发编程实战”系列的第12天&#xff01;今天我们将深入探讨阻塞队列&#xff08;BlockingQueue&#xff09;及其在线程协作中的应用。阻塞队列是Java并发编程中一个非常重要的工具&#xff0c;它不仅简化…