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

uniapp 实现低功耗蓝牙连接并读写数据实战指南

在物联网应用场景中,低功耗蓝牙(BLE)凭借其低能耗、连接便捷的特点,成为设备间数据交互的重要方式。Uniapp 作为一款跨平台开发框架,提供了丰富的 API 支持,使得在多个端实现低功耗蓝牙功能变得轻松高效。本文将结合示例代码,详细讲解如何在 Uniapp 中实现低功耗蓝牙的连接、数据读取与写入操作。

一、开发准备

在开始编码前,需确保开发环境已配置好 Uniapp 开发工具,同时要了解低功耗蓝牙的基本概念,如设备、服务、特征值等。设备是蓝牙连接的主体,服务是设备提供功能的集合,特征值则是具体的数据交互点,包含可读、可写等属性 。

二、初始化蓝牙模块

在 Uniapp 中,使用uni.openBluetoothAdapter方法初始化蓝牙模块,同时通过监听相关事件获取蓝牙状态变化和搜索到的设备信息。

const openBluetooth = () => {uni.openBluetoothAdapter({success: (e) => {console.log('蓝牙适配器打开成功');// 监听蓝牙适配器状态变化uni.onBluetoothAdapterStateChange((res) => {console.log('蓝牙适配器状态变化:', res);});// 监听搜索到新设备uni.onBluetoothDeviceFound((res) => {const devices = res.devices;console.log('搜索到新设备数量:', devices.length);// 处理搜索到的设备数据});},fail: (e) => {console.log('蓝牙适配器打开失败:', e);}});}

三、搜索蓝牙设备

调用uni.startBluetoothDevicesDiscovery方法开始搜索周围的蓝牙设备,可通过传入参数筛选特定设备。搜索完成后,及时调用uni.stopBluetoothDevicesDiscovery停止搜索,避免资源浪费。

const startDiscovery = () => {uni.startBluetoothDevicesDiscovery({success: (e) => {console.log('开始搜索蓝牙设备成功');},fail: (e) => {console.log('开始搜索蓝牙设备失败:', e);}});}const stopDiscovery = () => {uni.stopBluetoothDevicesDiscovery({success: (e) => {console.log('停止搜索蓝牙设备成功');},fail: (e) => {console.log('停止搜索蓝牙设备失败:', e);}});}

 

四、连接蓝牙设备

在获取到目标设备的deviceId后,使用uni.createBLEConnection方法连接设备,并监听连接状态变化。

const connectDevice = (deviceId) => {uni.createBLEConnection({deviceId: deviceId,success: (e) => {console.log('连接蓝牙设备成功');},fail: (e) => {console.log('连接蓝牙设备失败:', e);}});// 监听连接状态变化uni.onBLEConnectionStateChange((res) => {if (res.connected) {console.log('蓝牙设备已连接');} else {console.log('蓝牙设备已断开');}});}

五、获取设备服务与特征值

连接设备后,通过uni.getBLEDeviceServices获取设备服务列表,再利用uni.getBLEDeviceCharacteristics获取指定服务的特征值列表,区分出可读和可写的特征值。

const getServices = (deviceId) => {uni.getBLEDeviceServices({deviceId: deviceId,success: (res) => {const services = res.services;console.log('获取服务成功,服务数量:', services.length);// 处理服务数据},fail: (e) => {console.log('获取服务失败:', e);}});}const getCharacteristics = (deviceId, serviceId) => {uni.getBLEDeviceCharacteristics({deviceId: deviceId,serviceId: serviceId,success: (res) => {const characteristics = res.characteristics;console.log('获取特征值成功,特征值数量:', characteristics.length);// 处理特征值数据,区分可读可写},fail: (e) => {console.log('获取特征值失败:', e);}});}

六、数据读取与写入

6.1 读取数据

获取到可读特征值的characteristicId后,调用uni.readBLECharacteristicValue读取数据。

const readValue = (deviceId, serviceId, characteristicId) => {uni.readBLECharacteristicValue({deviceId: deviceId,serviceId: serviceId,characteristicId: characteristicId,success: (res) => {const data = res.value;console.log('读取数据成功:', data);},fail: (e) => {console.log('读取数据失败:', e);}});}

6.2 写入数据

对于可写特征值,将数据转换为合适格式后,使用uni.writeBLECharacteristicValue写入。

const writeValue = (deviceId, serviceId, characteristicId, data) => {// 数据格式转换,如将字符串转换为ArrayBufferconst buffer = new TextEncoder('utf - 8').encode(data);uni.writeBLECharacteristicValue({deviceId: deviceId,serviceId: serviceId,characteristicId: characteristicId,value: buffer,success: (res) => {console.log('写入数据成功');},fail: (e) => {console.log('写入数据失败:', e);}});}

七、断开连接与关闭蓝牙

使用uni.closeBLEConnection断开与设备的连接,通过uni.closeBluetoothAdapter关闭蓝牙模块。

const disconnectDevice = (deviceId) => {uni.closeBLEConnection({deviceId: deviceId,success: (e) => {console.log('断开蓝牙设备连接成功');},fail: (e) => {console.log('断开蓝牙设备连接失败:', e);}});}const closeBluetooth = () => {uni.closeBluetoothAdapter({success: (e) => {console.log('关闭蓝牙适配器成功');},fail: (e) => {console.log('关闭蓝牙适配器失败:', e);}});}

八、完整代码

<template><view class="container"><button @click="openBluetooth">初始化蓝牙模块</button><button @click="startDiscovery">开始搜索蓝牙设备</button><button @click="stopDiscovery">停止搜索蓝牙设备</button><view class="input-group"><text>设备:</text><input v-model="selectedDeviceName" disabled /><button @click="selectDevice">选择设备</button></view><button @click="connectDevice">连接蓝牙设备</button><button @click="getServices">获取设备服务</button><view class="input-group"><text>服务:</text><input v-model="selectedServiceId" disabled /><button @click="selectService">选择服务</button></view><button @click="getCharacteristics">获取服务的特征值</button><view class="input-group"><text>读取特征值:</text><input v-model="selectedCharacteristicId" disabled /><button @click="selectCharacteristic">选择</button></view><button @click="readValue">读取特征值数据</button><view class="input-group"><text>读取数据:</text><input v-model="readValueData" disabled style="width:60%" /></view><hr /><view class="input-group"><text>写入特征值:</text><input v-model="selectedWCharacteristicId" disabled /><button @click="selectwCharacteristic">选择</button></view><button @click="writeValue">写入特征值数据</button><view class="input-group"><text>写入数据:</text><input v-model="writeValueData" style="width:60%" /></view><button @click="disconnectDevice">断开蓝牙设备</button><button @click="closeBluetooth">关闭蓝牙模块</button><view class="output"><text>{{ outputText }}</text></view></view>
</template><script setup>import {ref} from 'vue'import {onLoad,onUnload} from '@dcloudio/uni-app'// 数据状态const bds = ref([]) // 可连接设备列表const deviceId = ref(null)const bconnect = ref(false)const bss = ref([]) // 连接设备服务列表const serviceId = ref(null)const bscs = ref([]) // 连接设备服务对应的特征值列表const characteristicId = ref(null)const bscws = ref([]) // 可写特征值列表const wcharacteristicId = ref(null)// UI绑定数据const selectedDeviceName = ref('')const selectedServiceId = ref('')const selectedCharacteristicId = ref('')const selectedWCharacteristicId = ref('')const readValueData = ref('')const writeValueData = ref('test')const outputText = ref('Bluetooth用于管理蓝牙设备,搜索附近蓝牙设备、连接实现数据通信等。')// 工具函数const buffer2hex = (value) => {let t = ''if (value) {const v = new Uint8Array(value)for (const i in v) {t += '0x' + v[i].toString(16) + ' '}} else {t = '无效值'}return t}const str2ArrayBuffer = (s, f) => {const b = new Blob([s], {type: 'text/plain'})const r = new FileReader()r.readAsArrayBuffer(b)r.onload = () => {if (f) f.call(null, r.result)}}// 重设数据const resetDevices = (d, s) => {if (!d) {bds.value = []deviceId.value = nullselectedDeviceName.value = ''}if (!s) {bss.value = []serviceId.value = nullselectedServiceId.value = ''}bscs.value = []bscws.value = []characteristicId.value = nullwcharacteristicId.value = nullselectedCharacteristicId.value = ''selectedWCharacteristicId.value = ''}// 输出日志const outLine = (text) => {outputText.value += '\n' + text}const outSet = (text) => {outputText.value = text}// 蓝牙操作函数const openBluetooth = () => {outSet('打开蓝牙适配器:')uni.openBluetoothAdapter({success: (e) => {outLine('打开成功!')// 监听蓝牙适配器状态变化uni.onBluetoothAdapterStateChange((res) => {outLine('onBluetoothAdapterStateChange: ' + JSON.stringify(res))})// 监听搜索到新设备uni.onBluetoothDeviceFound((res) => {const devices = res.devicesoutLine('onBluetoothDeviceFound: ' + devices.length)for (const i in devices) {outLine(JSON.stringify(devices[i]))const device = devices[i]if (device.deviceId) {bds.value.push(device)}}if (!bconnect.value && bds.value.length > 0) {const n = bds.value[bds.value.length - 1].nameselectedDeviceName.value = n || bds.value[bds.value.length - 1].deviceIddeviceId.value = bds.value[bds.value.length - 1].deviceId}})// 监听低功耗蓝牙设备连接状态变化uni.onBLEConnectionStateChange((res) => {outLine('onBLEConnectionStateChange: ' + JSON.stringify(res))if (deviceId.value === res.deviceId) {bconnect.value = res.connected}})// 监听低功耗蓝牙设备的特征值变化uni.onBLECharacteristicValueChange((res) => {outLine('onBLECharacteristicValueChange: ' + JSON.stringify(res))const value = buffer2hex(res.value)console.log(value)outLine('value(hex) = ' + value)if (characteristicId.value === res.characteristicId) {readValueData.value = value} else if (wcharacteristicId.value === res.characteristicId) {uni.showToast({title: value,icon: 'none'})}})},fail: (e) => {outLine('打开失败! ' + JSON.stringify(e))}})}const startDiscovery = () => {outSet('开始搜索蓝牙设备:')resetDevices()uni.startBluetoothDevicesDiscovery({success: (e) => {outLine('开始搜索成功!')},fail: (e) => {outLine('开始搜索失败! ' + JSON.stringify(e))}})}const stopDiscovery = () => {outSet('停止搜索蓝牙设备:')uni.stopBluetoothDevicesDiscovery({success: (e) => {outLine('停止搜索成功!')},fail: (e) => {outLine('停止搜索失败! ' + JSON.stringify(e))}})}const selectDevice = () => {if (bds.value.length <= 0) {uni.showToast({title: '未搜索到有效蓝牙设备!',icon: 'none'})return}const buttons = bds.value.map(device => {return device.name || device.deviceId})uni.showActionSheet({title: '选择蓝牙设备',itemList: buttons,success: (res) => {selectedDeviceName.value = bds.value[res.tapIndex].name || bds.value[res.tapIndex].deviceIddeviceId.value = bds.value[res.tapIndex].deviceIdoutLine('选择了"' + (bds.value[res.tapIndex].name || bds.value[res.tapIndex].deviceId) + '"')}})}const connectDevice = () => {if (!deviceId.value) {uni.showToast({title: '未选择设备!',icon: 'none'})return}outSet('连接设备: ' + deviceId.value)uni.createBLEConnection({deviceId: deviceId.value,success: (e) => {outLine('连接成功!')},fail: (e) => {outLine('连接失败! ' + JSON.stringify(e))}})}const getServices = () => {if (!deviceId.value) {uni.showToast({title: '未选择设备!',icon: 'none'})return}if (!bconnect.value) {uni.showToast({title: '未连接蓝牙设备!',icon: 'none'})return}resetDevices(true)outSet('获取蓝牙设备服务:')uni.getBLEDeviceServices({deviceId: deviceId.value,success: (e) => {const services = e.servicesoutLine('获取服务成功! ' + services.length)if (services.length > 0) {bss.value = servicesfor (const i in services) {outLine(JSON.stringify(services[i]))}if (bss.value.length > 0) {selectedServiceId.value = serviceId.value = bss.value[bss.value.length - 1].uuid}} else {outLine('获取服务列表为空?')}},fail: (e) => {outLine('获取服务失败! ' + JSON.stringify(e))}})}const selectService = () => {if (bss.value.length <= 0) {uni.showToast({title: '未获取到有效蓝牙服务!',icon: 'none'})return}const buttons = bss.value.map(service => service.uuid)uni.showActionSheet({title: '选择服务',itemList: buttons,success: (res) => {selectedServiceId.value = serviceId.value = bss.value[res.tapIndex].uuidoutLine('选择了服务: "' + serviceId.value + '"')}})}const getCharacteristics = () => {if (!deviceId.value) {uni.showToast({title: '未选择设备!',icon: 'none'})return}if (!bconnect.value) {uni.showToast({title: '未连接蓝牙设备!',icon: 'none'})return}if (!serviceId.value) {uni.showToast({title: '未选择服务!',icon: 'none'})return}resetDevices(true, true)outSet('获取蓝牙设备指定服务的特征值:')uni.getBLEDeviceCharacteristics({deviceId: deviceId.value,serviceId: serviceId.value,success: (e) => {const characteristics = e.characteristicsoutLine('获取特征值成功! ' + characteristics.length)if (characteristics.length > 0) {bscs.value = []bscws.value = []for (const i in characteristics) {const characteristic = characteristics[i]outLine(JSON.stringify(characteristic))if (characteristic.properties) {if (characteristic.properties.read) {bscs.value.push(characteristic)}if (characteristic.properties.write) {bscws.value.push(characteristic)if (characteristic.properties.notify || characteristic.properties.indicate) {uni.notifyBLECharacteristicValueChange({deviceId: deviceId.value,serviceId: serviceId.value,characteristicId: characteristic.uuid,state: true,success: (e) => {outLine('notifyBLECharacteristicValueChange ' +characteristic.uuid + ' success.')},fail: (e) => {outLine('notifyBLECharacteristicValueChange ' +characteristic.uuid + ' failed! ' + JSON.stringify(e))}})}}}}if (bscs.value.length > 0) {selectedCharacteristicId.value = characteristicId.value = bscs.value[bscs.value.length - 1].uuid}if (bscws.value.length > 0) {selectedWCharacteristicId.value = wcharacteristicId.value = bscws.value[bscws.value.length - 1].uuid}} else {outLine('获取特征值列表为空?')}},fail: (e) => {outLine('获取特征值失败! ' + JSON.stringify(e))}})}const selectCharacteristic = () => {if (bscs.value.length <= 0) {uni.showToast({title: '未获取到有效可读特征值!',icon: 'none'})return}const buttons = bscs.value.map(char => char.uuid)uni.showActionSheet({title: '选择特征值',itemList: buttons,success: (res) => {selectedCharacteristicId.value = characteristicId.value = bscs.value[res.tapIndex].uuidoutLine('选择了特征值: "' + characteristicId.value + '"')}})}let readInterval = nullconst readValue = () => {if (!deviceId.value) {uni.showToast({title: '未选择设备!',icon: 'none'})return}if (!bconnect.value) {uni.showToast({title: '未连接蓝牙设备!',icon: 'none'})return}if (!serviceId.value) {uni.showToast({title: '未选择服务!',icon: 'none'})return}if (!characteristicId.value) {uni.showToast({title: '未选择读取的特征值!',icon: 'none'})return}outSet('读取蓝牙设备的特征值数据: ')uni.readBLECharacteristicValue({deviceId: deviceId.value,serviceId: serviceId.value,characteristicId: characteristicId.value,success: (e) => {outLine('读取数据成功!')},fail: (e) => {outLine('读取数据失败! ' + JSON.stringify(e))}})}const selectwCharacteristic = () => {if (bscws.value.length <= 0) {uni.showToast({title: '未获取到有效可写特征值!',icon: 'none'})return}const buttons = bscws.value.map(char => char.uuid)uni.showActionSheet({title: '选择特征值',itemList: buttons,success: (res) => {selectedWCharacteristicId.value = wcharacteristicId.value = bscws.value[res.tapIndex].uuidoutLine('选择了特征值: "' + wcharacteristicId.value + '"')}})}const writeValue = () => {if (!deviceId.value) {uni.showToast({title: '未选择设备!',icon: 'none'})return}if (!bconnect.value) {uni.showToast({title: '未连接蓝牙设备!',icon: 'none'})return}if (!serviceId.value) {uni.showToast({title: '未选择服务!',icon: 'none'})return}if (!wcharacteristicId.value) {uni.showToast({title: '未选择写入的特征值!',icon: 'none'})return}if (!writeValueData.value || writeValueData.value === '') {uni.showToast({title: '请输入需要写入的数据',icon: 'none'})return}str2ArrayBuffer(writeValueData.value, (buffer) => {outSet('写入蓝牙设备的特征值数据: ')uni.writeBLECharacteristicValue({deviceId: deviceId.value,serviceId: serviceId.value,characteristicId: wcharacteristicId.value,value: buffer,success: (e) => {outLine('写入数据成功!')},fail: (e) => {outLine('写入数据失败! ' + JSON.stringify(e))}})})}const disconnectDevice = () => {if (!deviceId.value) {uni.showToast({title: '未选择设备!',icon: 'none'})return}resetDevices(true)outSet('断开蓝牙设备连接:')uni.closeBLEConnection({deviceId: deviceId.value,success: (e) => {outLine('断开连接成功!')},fail: (e) => {outLine('断开连接失败! ' + JSON.stringify(e))}})}const closeBluetooth = () => {outSet('关闭蓝牙适配器:')resetDevices()uni.closeBluetoothAdapter({success: (e) => {outLine('关闭成功!')bconnect.value = false},fail: (e) => {outLine('关闭失败! ' + JSON.stringify(e))}})}
</script><style>.container {padding: 20px;}button {margin: 10px 0;padding: 10px;background-color: #007aff;color: white;border-radius: 5px;border: none;}.input-group {display: flex;align-items: center;margin: 10px 0;}.input-group input {flex: 1;border: 1px solid #ccc;padding: 5px;margin: 0 5px;}.input-group button {margin: 0;padding: 5px 10px;}.output {margin-top: 20px;padding: 10px;background-color: #f5f5f5;border: 1px solid #ddd;white-space: pre-wrap;max-height: 200px;overflow-y: auto;}
</style>

 

九、注意事项

      1、权限问题:在不同平台上,需确保应用已获取蓝牙相关权限。例如在 Android 平台,需在AndroidManifest.xml中添加蓝牙权限声明。

      2、兼容性:不同设备的蓝牙服务和特征值 UUID 可能不同,需根据实际设备文档进行适配。

      3、错误处理:完善各 API 调用的错误处理逻辑,及时向用户反馈操作结果。

以上就是在 Uniapp 中实现低功耗蓝牙连接并读写数据的完整流程。若你在实践中有优化需求或遇到问题,欢迎随时分享,我们一起探讨解决。

 

相关文章:

  • 【Fifty Project - D21】
  • 阿里云服务器技术纵览:从底层架构到行业赋能​
  • 海外社交软件开发实战:从架构设计到合规落地的技术解析
  • 【数学建模国奖速成系列】优秀论文绘图复现代码(四)
  • C++漫游指南——字符串篇与内存分配篇
  • XML文件中`<![CDATA[...]]>` 的写法
  • 第五届图像、视觉与智能系统国际会议(ICIVIS 2025)参会通知
  • 【每日八股】复习 Redis Day3:Redis 的应用
  • 数据结构篇:线性表的另一表达—链表之单链表(下篇)
  • canvas动画:点随机运动 距离内自动连接成线 鼠标移动自动吸附附近的点
  • 销售与金融领域的数据处理与分析方法
  • 大连理工大学选修课——机器学习笔记(3):KNN原理及应用
  • 机器学习实操 第一部分 机器学习基础 第7章 集成学习与随机森林
  • 股指期货贴水对对冲的影响大吗?
  • Python实例题:Python实现简易局域网视频聊天工具
  • LeetCode算法题 (除自身以外数组的乘积)Day14!!!C/C++
  • 日语学习-日语知识点小记-构建基础-JLPT-N4阶段(12): ておき ます
  • 网页出现502的报错是什么意思?
  • 5、SpringBoot整合RabbitMQ
  • 楼宇智能化三、五章【期末复习】
  • 49:49白热化,美参议院对新关税政策产生巨大分歧
  • 建设银行南昌分行引金融“活水”,精准灌溉乡村沃土
  • 李铁案二审驳回上诉,维持一审有期徒刑20年的判决
  • 违规行为屡禁不止、责任边界模糊不清,法治日报:洞穴探险,谁为安全事故买单?
  • 南京航空航天大学启动扁平化改革:管理岗规模控制在20%,不再统一设科级机构
  • 辽宁省全力开展辽阳一饭店火灾事故救援处置工作