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

news2025/7/23 12:43:11

      本教程适用于使用 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.coloradmin.cn/o/2402536.html

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

相关文章

三种读写传统xls格式文件开源库libxls、xlslib、BasicExcel的比较

最近准备读写传统xls格式文件&#xff0c;而不是较新的xlsx&#xff0c;询问DeepSeek有哪些开源库&#xff0c;他给出了如下的简介和建议&#xff0c;还给出了相应链接&#xff0c;不过有的链接已失效。最后还不忘提醒&#xff0c;现在该用xlsx格式了。 以下是几个可以处理传统…

Nature子刊同款的宏基因组免疫球蛋白测序怎么做?

免疫球蛋白A&#xff08;IgA&#xff09;是人体肠道黏膜分泌的主要抗体&#xff0c;它在塑造肠道微生物群落和维持肠道稳态中起着关键作用&#xff0c;有研究发现缺乏IgA的患者更容易患自身免疫性疾病和感染性疾病。 目前用于研究IgA结合的主要技术是IgA-SEQ&#xff0c;结合了…

2025年牛客网秋招/社招高质量 Java 面试八股文整理

Java 面试 不论是校招还是社招都避免不了各种面试。笔试&#xff0c;如何去准备这些东西就显得格外重要。不论是笔试还是面试都是有章可循的。关键在于理解企业的需求&#xff0c;明确自己的定位&#xff0c;以及掌握一定的应试技巧。 笔试部分&#xff0c;通常是对基础知识、…

ADI的BF609双核DSP怎么做开发,我来说一说(五)LAN口测试

作者的话 ADI的双核DSP&#xff0c;第二颗是Blackfin系列的BF609&#xff0c;这颗DSP我用了很久&#xff0c;比较熟悉&#xff0c;且写过一些给新手的教程。 硬件准备 ADSP-BF609-CORE&#xff1a;ADI BF609开发板 产品链接&#xff1a;https://item.taobao.com/item.htm?…

行业赋能篇-2-能源行业安全运维升级

在能源行业&#xff0c;尤其是风电领域&#xff0c;运维作业往往面临“三高”挑战——高风险环境、高异构数据量&#xff09;、高合规要求。以海上风电场为例&#xff0c;传统运维依赖卫星电话沟通&#xff0c;数据记录碎片化&#xff0c;故障因信息传递延迟导致损失扩大。如何…

飞云智能波段主图+多空短线决策副图指标,组合操盘技术图文解说

如上图&#xff0c;组合指标&#xff1a;主图-飞云智能波段&#xff0c;红线上红色K线标记&#xff0c;波段做多.副图指标-多空短线决策&#xff0c;跟踪做短线&#xff0c;红柱做多&#xff0c;绿柱短线卖出或做空。 实战操作中&#xff0c;我们在主图红色线支撑上红色K线出现…

【51单片机】1. 基础点灯大师

1. 新建一个项目集一些基本操作 打开Keli软件&#xff0c;然后&#xff1a; 【Project】→【new μVision Project】→选择项目保存位置 建议文件名选一些通用的名字&#xff0c;如【Project】 左下角选择【Atmel】的【AT89C52】 弹出的【是否添加启动文件到文件夹下】&…

PC端直接打印功能(包括两张图片合并功能)

一、 效果图 二、demo代码 <template><div class"box"><divref"printContent"class"print-content"><div class"print-title">打印图片</div><imgclass"preview-image":src"merged…

Vue前端篇——项目目录结构介绍

&#x1f4d8; 前言 在正式开始学习 Vue 3 开发之前&#xff0c;了解并熟悉其项目目录结构是非常关键的第一步。一个清晰、规范的目录结构不仅有助于开发者高效地组织代码&#xff0c;还能显著提升项目的可读性和可维护性。 Vue 3 作为现代前端开发中广泛使用的主流框架之一&…

抽象工厂模式深度解析:从原理到与应用实战

作者简介 我是摘星&#xff0c;一名全栈开发者&#xff0c;专注 Java后端开发、AI工程化 与 云计算架构 领域&#xff0c;擅长Python技术栈。热衷于探索前沿技术&#xff0c;包括大模型应用、云原生解决方案及自动化工具开发。日常深耕技术实践&#xff0c;乐于分享实战经验与…

35.成功解决编写关于“江协科技”编写技巧第二期标志位积累的问题

江科大学长又发布了第二期的编写技巧&#xff01; 大家可以看看&#xff1a;https://space.bilibili.com/383400717 最后面给了一个未完成的任务&#xff1a; 这里我已经把这个问题给解决了&#xff01; 总代码放在资源里面&#xff0c;key.c放在文章最后面&#xff01;同时感…

Linux常用命令学习手册

Linux常用命令学习手册https://download.csdn.net/download/2401_87690752/90953550 《Linux常用命令学习手册》提供了一份实用的Linux操作指南&#xff0c;主要收录了系统管理和文件操作等基础命令。内容涵盖了目录切换、文件查看、权限设置等核心功能&#xff0c;适合Linux初…

Tailwind CSS 实战:基于 Kooboo 构建 AI 对话框页面(八):异步处理逻辑详解

在现代 Web 应用中&#xff0c;异步处理是实现流畅交互的核心技术。本文基于前几章实现的内容Tailwind CSS 实战&#xff1a;基于 Kooboo 构建 AI 对话框页面&#xff08;七&#xff09;&#xff1a;消息框交互功能添加-CSDN博客&#xff0c;深入解析 AI 对话框页面中异步逻辑的…

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

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

COMSOL与MATLAB联合仿真人工智能的电学层析成像系统

关键词&#xff1a;MATLAB&#xff0c;电学层析成像&#xff0c;人工智能&#xff0c;图像重建&#xff0c;深度学习 一、引言 基于人工智能的电学层析成像系统是一种创新的检测技术&#xff0c;结合了电学层析成像技术与人工智能算法的优势。电学层析成像技术&#xff0c;简…

配置sudo免密却不生效的问题

如图&#xff0c;我配置了dhcp4这个账号sudo免密&#xff0c;但是执行sudo的时候还是要输密码。 查看dhcp的用户组&#xff0c;是配置了一个wheel组&#xff0c;而wheel组配置的是需要密码。 我们用dhcp4用户执行sudo -l 发下他匹配了两条命令策略&#xff0c;一个是免密一个…

大模型赋能:金融智能革命中的特征工程新纪元

一、AI进化论&#xff1a;从“判别”到“生成”的金融新战场 1.1 判别式AI的“痛点”与大模型的“破局” 想象这样一幅画面&#xff1a;银行风控模型像老式收音机&#xff0c;需要人工反复调试参数才能捕捉风险信号&#xff1b;而大模型则是智能调音台&#xff0c;能自动“听…

LHA9924芯片可代替AD7190,CS5530

LHA9924是一款高性能、单芯片模数转换器(ADC)。该器件包括一个低噪声可编程增益放大器(PGA)、Δ-Σ调制器和数字滤波器。该ADC支持两种运行模式&#xff0c;可在功耗与分辨率之间实现最佳平衡。双通道多路复用器可以选择外部信号测量和内部ADC测试信号。具有使输入电路短路来测…

短视频矩阵系统技术saas源头6年开发构架

在短视频内容爆发式增长的今天&#xff0c;短视频矩阵系统SAAS技术成为企业快速搭建视频平台的关键解决方案。本文将系统解析从技术源头到服务落地的全流程开发路径。 一、系统定义与技术基础 短视频矩阵系统是集视频上传、智能编辑、多端分享、高清播放于一体的综合性平台。其…

前端删除评论操作(局部更新数组)

​评论的删除是局部删除&#xff0c;把所点击的评论id号传递给后端&#xff0c;通知后端在数据库中删除数据&#xff0c;并且返回数据&#xff0c;但是在前端并不直接接收返回的数据&#xff0c;而是触发回调事件&#xff0c;在前端上进行删除评论&#xff0c;首先通过pId观察他…