最新要闻

广告

手机

iphone11大小尺寸是多少?苹果iPhone11和iPhone13的区别是什么?

iphone11大小尺寸是多少?苹果iPhone11和iPhone13的区别是什么?

警方通报辅警执法直播中被撞飞:犯罪嫌疑人已投案

警方通报辅警执法直播中被撞飞:犯罪嫌疑人已投案

家电

焦点观察:记录--『uni-app、小程序』蓝牙连接、读写数据全过程

来源:博客园

这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助

本文简介

这是一次真实的 蓝牙收发数据 的全过程讲解。


(资料图片仅供参考)

本文使用 uni-app + Vue3 的方式进行开发,以手机app的方式运行(微信小程序同样可行)。

uni-app 提供了 蓝牙 和 低功耗蓝牙 的 api ,和微信小程序提供的 api 是一样的,所以本文的讲解也适用于微信小程序。

本文只实现 蓝牙收发数据 功能,至于样式,我懒得调~

蓝牙相关功能我会逐步讲解。如果你基础好,又急的话,可以直接跳到 『完整代码』的章节查看,那里没废话。

环境说明

开发工具:HBuilder X 3.4.7.20220422uni-app + Vue3以安卓App的方式运行(iOS和小程序同理)

思路

蓝牙收发数据的逻辑和我们常用的 AJAX 进行的网络请求是有一丢丢不同的。

其中较大的区别是:蓝牙接收数据不是那么的稳定,相比起网络请求,蓝牙更容易出现丢包的情况。

在开发中,AJAX 发起的请求不管成功还是失败,浏览器基本都会给你一个答复。但 uni-app 提供的 api 来看,蓝牙接收数据会显得更加 “异步” 。

大致思路

使用蓝牙进行数据传输的大概思路如下:

初始化:打开蓝牙模块搜寻:检测附近存在的设备连接:找到目标设备进行监听:开启监听功能,接收其他设备传过来的数据发送指令:不管发送数据还是读取数据,都可以理解为向外发送指令

实现

上面整理出使用蓝牙传输数据的5大动作,但每个动作其实都是由 uni-app 提供的一个或者多个 api 组合而成。

初始化阶段

使用蓝牙之前,需要初始化蓝牙模块,这是最最最开始就要做的!

使用 uni.openBluetoothAdapter 这个 api 就可以初始化蓝牙模块。其他蓝牙相关 API 必须在 uni.openBluetoothAdapter 调用之后使用。否则 API 会返回错误( errCode=10000 )。

代码示例

​<script setup> ​// 【1】初始化蓝牙function initBlue() {    uni.openBluetoothAdapter({        success(res) {            console.log("初始化蓝牙成功")            console.log(res)        },        fail(err) {            console.log("初始化蓝牙失败")            console.error(err)        }    })} </script> 

如果你手机开启了蓝牙,点击页面上的按钮后,控制台就会输出如下内容

初始化蓝牙成功{"errMsg":"openBluetoothAdapter:ok"} 

如果手机没开启蓝牙,就会返回如下内容

初始化蓝牙失败{"errMsg":"openBluetoothAdapter:fail not available","code":10001} 

根据文档提示,10001代表当前蓝牙适配器不可用。

如果你的控制台能打印出 {"errMsg":"openBluetoothAdapter:ok"} 证明第一步已经成功了。

接下来可以开始搜索附近蓝牙设备。

搜寻附近设备

这一步需要2个 api 配合完成。所以可以分解成以下2步:

开启搜寻功能:uni.startBluetoothDevicesDiscovery监听搜寻到新设备:uni.onBluetoothDeviceFound开发蓝牙相关功能时,操作逻辑更像是推送,所以“开启搜索”和“监听新设备”是分开操作的。

uni.startBluetoothDevicesDiscovery 可以让设备开始搜索附近蓝牙设备,但这个方法比较耗费系统资源,建议在连接到设备之后就使用 uni.stopBluetoothDevicesDiscovery 停止继续搜索。

uni.startBluetoothDevicesDiscovery 方法里可以传入一个对象,该对象接收几个参数,但初学的话我们只关注 success 和 fail。如果你的项目中硬件佬有提供 service 的 uuid 给你的话,你也可以在 services 里传入。其他参数可以查看官方文档的介绍。

在使用 uni.startBluetoothDevicesDiscovery (开始搜索)后,可以使用 uni.onBluetoothDeviceFound 进行监听,这个方法里面接收一个回调函数。

代码示例

<script setup> import { ref } from "vue"// 搜索到的蓝牙设备列表const blueDeviceList = ref([])// 【1】初始化蓝牙function initBlue() {    uni.openBluetoothAdapter({        success(res) {            console.log("初始化蓝牙成功")            console.log(res)        },        fail(err) {            console.log("初始化蓝牙失败")            console.error(err)        }    })}// 【2】开始搜寻附近设备function discovery() {    uni.startBluetoothDevicesDiscovery({        success(res) {            console.log("开始搜索")                        // 开启监听回调            uni.onBluetoothDeviceFound(found)        },        fail(err) {            console.log("搜索失败")            console.error(err)        }    })}// 【3】找到新设备就触发该方法function found(res) {    console.log(res)    blueDeviceList.value.push(res.devices[0])} </script> 

上面代码的逻辑是,如果开启“寻找附近设备”功能成功,接着就开启“监听寻找到新设备的事件”。

搜索到的设备会返回以下数据:

{    "devices": [{        "deviceId": "B4:10:7B:C4:83:14",        "name": "蓝牙设备名",        "RSSI": -58,        "localName": "",        "advertisServiceUUIDs": ["0000FFF0-0000-1000-8000-00805F9B34FB"],        "advertisData": {}    }]} 

每监听到一个新的设备,我都会将其添加到 蓝牙设备列表(blueDeviceList) 里,最后讲这个列表的数据渲染到页面上。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cePpaik0-1652253685142)(https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/f3a21f372d3b40e996e3a0db9f4aa66b~tplv-k3u1fbpfcp-zoom-in-crop-mark:1956:0:0:0.image?)]

连接目标设备

连接目标设备只需要1个 api 就能完成。但根据文档提示,我们连接后还需要关闭 “搜索附近设备” 的功能,这个很好理解,既然找到了,再继续找就是浪费资源。

流程如下:

获取设备ID:根据 uni.onBluetoothDeviceFound 回调,拿到设备ID连接设备:使用设备ID进行连接 uni.createBLEConnection停止搜索:uni.stopBluetoothDevicesDiscovery我给每条搜索到的蓝牙结果添加一个 click 事件,会向目标设备发送连接请求。

我的设备名称是 leihou ,所以我点击了这条。

代码示例

​<script setup> import { ref } from "vue"​// 搜索到的蓝牙设备列表const blueDeviceList = ref([])​// 【1】初始化蓝牙function initBlue() {    uni.openBluetoothAdapter({        success(res) {            console.log("初始化蓝牙成功")            console.log(res)        },        fail(err) {            console.log("初始化蓝牙失败")            console.error(err)        }    })}​// 【2】开始搜寻附近设备function discovery() {    uni.startBluetoothDevicesDiscovery({        success(res) {            console.log("开始搜索")            // 开启监听回调            uni.onBluetoothDeviceFound(found)        },        fail(err) {            console.log("搜索失败")            console.error(err)        }    })}​// 【3】找到新设备就触发该方法function found(res) {    console.log(res)    blueDeviceList.value.push(res.devices[0])}​// 蓝牙设备的idconst deviceId = ref("")​// 【4】连接设备function connect(data) {    console.log(data)​    deviceId.value = data.deviceId​    uni.createBLEConnection({        deviceId: deviceId.value,        success(res) {            console.log("连接成功")            console.log(res)            // 停止搜索            stopDiscovery()        },        fail(err) {            console.log("连接失败")            console.error(err)        }    })}​// 【5】停止搜索function stopDiscovery() {    uni.stopBluetoothDevicesDiscovery({        success(res) {            console.log("停止成功")            console.log(res)        },        fail(err) {            console.log("停止失败")            console.error(err)        }    })} </script>​ 

连接成功后在控制台会输出

连接成功{"errMsg":"createBLEConnection:ok"} 

在连接成功后就立刻调用uni.stopBluetoothDevicesDiscovery方法停止继续搜索附近其他设备,停止成功后会输出

停止成功{"errMsg":"stopBluetoothDevicesDiscovery:ok"} 

监听

在连接完设备后,就要先开启监听数据的功能。这样才能接收到发送读写指令后设备给你回调的信息。

要开启监听,首先需要知道蓝牙设备提供了那些服务,然后通过服务获取特征值,特征值会告诉你哪个可读,哪个可写。最后根据特征值进行消息监听。

步骤如下:

获取蓝牙设备服务:uni.getBLEDeviceServices获取特征值:uni.getBLEDeviceCharacteristics开启消息监听:uni.notifyBLECharacteristicValueChange接收消息监听传来的数据:uni.onBLECharacteristicValueChange正常情况下,硬件佬会提前把蓝牙设备的指定服务还有特征值告诉你。

比如我这个设备的蓝牙服务是:0000FFE0-0000-1000-8000-00805F9B34FB

特征值是:0000FFE1-0000-1000-8000-00805F9B34FB

第一步,获取蓝牙服务

​<script setup> import { ref } from "vue"​// 省略上一步的代码……​// 【6】获取服务function getServices() {    uni.getBLEDeviceServices({        deviceId: deviceId.value, // 设备ID,在上一步【4】里获取        success(res) {            console.log(res)        },        fail(err) {            console.error(err)        }    })} </script> 

此时点击按钮,将会获取到已连接设备的所有服务。

我的设备有以下几个服务。你在工作中拿到的服务uuid和我的是不一样的,数量也不一定相同。

可以发现,我拿到的结果里有0000FFE0-0000-1000-8000-00805F9B34FB这条服务。

{    "services": [{        "uuid": "00001800-0000-1000-8000-00805F9B34FB",        "isPrimary": true    }, {        "uuid": "00001801-0000-1000-8000-00805F9B34FB",        "isPrimary": true    }, {        "uuid": "0000180A-0000-1000-8000-00805F9B34FB",        "isPrimary": true    }, {        "uuid": "0000FFF0-0000-1000-8000-00805F9B34FB",        "isPrimary": true    }, {        "uuid": "0000FFE0-0000-1000-8000-00805F9B34FB",        "isPrimary": true    }],    "errMsg": "getBLEDeviceServices:ok"} 

第二步,获取指定服务的特征值

获取特征值,需要传设备ID和服务ID。

在上两步我拿到了设备ID为B4:10:7B:C4:83:14,服务ID为0000FFE0-0000-1000-8000-00805F9B34FB

​<script setup> import { ref } from "vue"​// 省略前面几步代码​// 【7】获取特征值function getCharacteristics() {    uni.getBLEDeviceCharacteristics({        deviceId: deviceId.value, // 设备ID,在【4】里获取到        serviceId: "0000FFE0-0000-1000-8000-00805F9B34FB", // 服务UUID,在【6】里能获取到        success(res) {            console.log(res)        },        fail(err) {            console.error(err)        }    })} </script> 

最后成功输出

{    "characteristics": [{        "uuid": "0000FFE1-0000-1000-8000-00805F9B34FB",        "properties": {            "read": true,            "write": true,            "notify": true,            "indicate": false        }    }],    "errMsg": "getBLEDeviceCharacteristics:ok"} 

characteristics 字段里保存了该服务的所有特征值,我的设备这个服务只有1个特征值,并且读、写、消息推送都为 true。

你的设备可能不止一条特征值,需要监听那条特征值这需要你和硬件佬协商的(通常也是硬件佬直接和你说要监听哪条)。

第三、四步,开启消息监听 并 接收消息监听传来的数据

根据已经拿到的 设备ID、服务ID、特征值,就可以开启对应的监听功能。

使用 uni.notifyBLECharacteristicValueChange 开启消息监听;

并在 uni.onBLECharacteristicValueChange 方法触发监听到的消息。

​<script setup> import { ref } from "vue"​// 省略前面几步代码​// 【8】开启消息监听function notify() {    uni.notifyBLECharacteristicValueChange({        deviceId: deviceId.value, // 设备ID,在【4】里获取到        serviceId: "0000FFE0-0000-1000-8000-00805F9B34FB", // 服务UUID,在【6】里能获取到        characteristicId: "0000FFE1-0000-1000-8000-00805F9B34FB", // 特征值,在【7】里能获取到        success(res) {            console.log(res)                        // 接受消息的方法            listenValueChange()        },        fail(err) {            console.error(err)        }    })}​// ArrayBuffer转16进度字符串示例function ab2hex(buffer) {  const hexArr = Array.prototype.map.call(    new Uint8Array(buffer),    function (bit) {      return ("00" + bit.toString(16)).slice(-2)    }  )  return hexArr.join("")}​// 将16进制的内容转成我们看得懂的字符串内容function hexCharCodeToStr(hexCharCodeStr) {    var trimedStr = hexCharCodeStr.trim();    var rawStr = trimedStr.substr(0, 2).toLowerCase() === "0x" ? trimedStr.substr(2) : trimedStr;    var len = rawStr.length;    if (len % 2 !== 0) {            alert("存在非法字符!");            return "";    }    var curCharCode;    var resultStr = [];    for (var i = 0; i < len; i = i + 2) {            curCharCode = parseInt(rawStr.substr(i, 2), 16);            resultStr.push(String.fromCharCode(curCharCode));    }    return resultStr.join("");}​// 【9】监听消息变化function listenValueChange() {    uni.onBLECharacteristicValueChange(res => {        // 结果        console.log(res)                // 结果里有个value值,该值为 ArrayBuffer 类型,所以在控制台无法用肉眼观察到,必须将该值转换为16进制        let resHex = ab2hex(res.value)        console.log(resHex)​        // 最后将16进制转换为ascii码,就能看到对应的结果        let result = hexCharCodeToStr(resHex)        console.log(result)    })} </script> 

listenValueChange方法是用来接收设备传过来的消息。

上面的例子中,res的结果是

{    "deviceId": "B4:10:7B:C4:83:14",    "serviceId": "0000FFE0-0000-1000-8000-00805F9B34FB",    "characteristicId": "0000FFE1-0000-1000-8000-00805F9B34FB",    "value": {}} 

设备传过来的内容就放在 value 字段里,但因为该字段的类型是 ArrayBuffer,所以无法在控制台用肉眼直接观察。于是就通过 ab2hex 方法将该值转成 16进制 ,最后再用 hexCharCodeToStr 方法将 16进制 转成 ASCII码。

我从设备里发送一段字符串过来:leihou

App端收到的数据转成 16进制 后的结果:6c6569686f75

再从 16进制 转成 ASCII码 后的结果:leihou

发送指令

终于到最后一步了。

从 uni-app 和 微信小程序 提供的蓝牙api 来看,发送指令只要有2个方法:

uni.writeBLECharacteristicValue:向低功耗蓝牙设备特征值中写入二进制数据。uni.readBLECharacteristicValue:读取低功耗蓝牙设备的特征值的二进制数据值。这里需要理清一个概念,本节的内容为 “发送指令” ,也就是说,从你的app或小程序向其他蓝牙设备发送指令,而这个指令分2种情况,一种是你要发送一些数据给蓝牙设备,另一种情况是你叫蓝牙设备给你发点信息。

uni.writeBLECharacteristicValue这两种情况我们需要分开讨论,先讲讲 uni.writeBLECharacteristicValue 。

uni.writeBLECharacteristicValue 从文档可以看出,这个 api 是可以发送一些数据给蓝牙设备,但发送的值要转成 ArrayBuffer 。

代码示例

​<script setup> import { ref } from "vue"​// 省略前面几步代码​// 【10】发送数据function send() {    // 向蓝牙设备发送一个0x00的16进制数据        let msg = "hello"        const buffer = new ArrayBuffer(msg.length)    const dataView = new DataView(buffer)    // dataView.setUint8(0, 0)        for (var i = 0; i < msg.length; i++) {      dataView.setUint8(i, msg.charAt(i).charCodeAt())    }        uni.writeBLECharacteristicValue({      deviceId: deviceId.value, // 设备ID,在【4】里获取到      serviceId: "0000FFE0-0000-1000-8000-00805F9B34FB", // 服务UUID,在【6】里能获取到      characteristicId: "0000FFE1-0000-1000-8000-00805F9B34FB", // 特征值,在【7】里能获取到      value: buffer,      success(res) {        console.log(res)      },      fail(err) {        console.error(err)      }    })} </script> 

此时,如果 uni.writeBLECharacteristicValue 走 success ,证明你已经把数据向外成功发送了,但不代表设备一定就收到了。

通常设备收到你发送过去的信息,会返回一条消息给你,而这个回调消息会在 uni.onBLECharacteristicValueChange 触发,也就是 第【9】步 那里。但这是蓝牙设备那边控制的,你作为前端佬,人家“已读不回”你也拿人家没办法。

uni.readBLECharacteristicValue在 “监听” 部分,我们使用了 uni.getBLEDeviceCharacteristics 获取设备的特征值,我的设备提供的特征值支持 read ,所以可以使用 uni.readBLECharacteristicValue 向蓝牙设备发送一条 “读取” 指令。然后在 uni.onBLECharacteristicValueChange 里可以接收设备发送过来的数据。

代码示例

​<script setup> import { ref } from "vue"​// 省略前面几步代码​// 【11】读取数据function read() {    uni.readBLECharacteristicValue({        deviceId: deviceId.value,        serviceId: serviceId.value,        characteristicId: characteristicId.value,        success(res) {            console.log("读取指令发送成功")            console.log(res)        },        fail(err) {            console.log("读取指令发送失败")            console.error(err)        }    })} </script> 

使用 “读取” 的方式向设备发送指令,是不需要另外传值的。

此时我的设备返回00

这个数据是硬件那边设置的。

在日常工作中,uni.readBLECharacteristicValue 的作用主要是读取数据,但使用场景不算很多。

我在工作中遇到的场景是:蓝牙设备提供了几个接口,而且传过来的数据比较大,比如传图片给app这边。我就会先用 uni.writeBLECharacteristicValue 告诉设备我现在需要取什么接口的数据,然后用 uni.readBLECharacteristicValue 发送读取数据的请求,如果数据量比较大,就要重复使用 uni.readBLECharacteristicValue 进行读取。比如上面的例子,我读第一次的时候返回 00 ,读第二次就返回 01 ……

最后再提醒一下,uni.readBLECharacteristicValue 只负责发送读取的请求,并且里面的 success 和 fail 只是返回你本次发送请求的动作是否成功,至于对面的蓝牙设备有没有收到这个指令你是不清楚的。

最后需要通过 uni.getBLEDeviceCharacteristics 监听设备传过来的数据。

完整代码

​<script setup> import { ref } from "vue"​// 搜索到的蓝牙设备列表const blueDeviceList = ref([])​// 【1】初始化蓝牙function initBlue() {    uni.openBluetoothAdapter({        success(res) {            console.log("初始化蓝牙成功")            console.log(res)        },        fail(err) {            console.log("初始化蓝牙失败")            console.error(err)        }    })}​// 【2】开始搜寻附近设备function discovery() {    uni.startBluetoothDevicesDiscovery({        success(res) {            console.log("开始搜索")            // 开启监听回调            uni.onBluetoothDeviceFound(found)        },        fail(err) {            console.log("搜索失败")            console.error(err)        }    })}​// 【3】找到新设备就触发该方法function found(res) {    console.log(res)    blueDeviceList.value.push(res.devices[0])}​// 蓝牙设备的idconst deviceId = ref("")​// 【4】连接设备function connect(data) {    console.log(data)        deviceId.value = data.deviceId // 将获取到的设备ID存起来        uni.createBLEConnection({        deviceId: deviceId.value,        success(res) {            console.log("连接成功")            console.log(res)            // 停止搜索            stopDiscovery()            uni.showToast({                title: "连接成功"            })        },        fail(err) {            console.log("连接失败")            console.error(err)            uni.showToast({                title: "连接成功",                icon: "error"            })        }    })}​// 【5】停止搜索function stopDiscovery() {    uni.stopBluetoothDevicesDiscovery({        success(res) {            console.log("停止成功")            console.log(res)        },        fail(err) {            console.log("停止失败")            console.error(err)        }    })}​// 【6】获取服务function getServices() {    // 如果是自动链接的话,uni.getBLEDeviceServices方法建议使用setTimeout延迟1秒后再执行    uni.getBLEDeviceServices({        deviceId: deviceId.value,        success(res) {            console.log(res) // 可以在res里判断有没有硬件佬给你的服务            uni.showToast({                title: "获取服务成功"            })        },        fail(err) {            console.error(err)            uni.showToast({                title: "获取服务失败",                icon: "error"            })        }    })}​// 硬件提供的服务id,开发中需要问硬件佬获取该idconst serviceId = ref("0000FFE0-0000-1000-8000-00805F9B34FB")​// 【7】获取特征值function getCharacteristics() {    // 如果是自动链接的话,uni.getBLEDeviceCharacteristics方法建议使用setTimeout延迟1秒后再执行    uni.getBLEDeviceCharacteristics({        deviceId: deviceId.value,        serviceId: serviceId.value,        success(res) {            console.log(res) // 可以在此判断特征值是否支持读写等操作,特征值其实也需要提前向硬件佬索取的            uni.showToast({                title: "获取特征值成功"            })        },        fail(err) {            console.error(err)            uni.showToast({                title: "获取特征值失败",                icon: "error"            })        }    })}​const characteristicId = ref("0000FFE1-0000-1000-8000-00805F9B34FB")​// 【8】开启消息监听function notify() {    uni.notifyBLECharacteristicValueChange({        deviceId: deviceId.value, // 设备id        serviceId: serviceId.value, // 监听指定的服务        characteristicId: characteristicId.value, // 监听对应的特征值        success(res) {            console.log(res)            listenValueChange()            uni.showToast({                title: "已开启监听"            })        },        fail(err) {            console.error(err)            uni.showToast({                title: "监听失败",                icon: "error"            })        }    })}​// ArrayBuffer转16进度字符串示例function ab2hex(buffer) {  const hexArr = Array.prototype.map.call(    new Uint8Array(buffer),    function (bit) {      return ("00" + bit.toString(16)).slice(-2)    }  )  return hexArr.join("")}​// 将16进制的内容转成我们看得懂的字符串内容function hexCharCodeToStr(hexCharCodeStr) {    var trimedStr = hexCharCodeStr.trim();    var rawStr = trimedStr.substr(0, 2).toLowerCase() === "0x" ? trimedStr.substr(2) : trimedStr;    var len = rawStr.length;    if (len % 2 !== 0) {            alert("存在非法字符!");            return "";    }    var curCharCode;    var resultStr = [];    for (var i = 0; i < len; i = i + 2) {            curCharCode = parseInt(rawStr.substr(i, 2), 16);            resultStr.push(String.fromCharCode(curCharCode));    }    return resultStr.join("");}​// 监听到的内容const message = ref("")const messageHex = ref("") // 十六进制​// 【9】监听消息变化function listenValueChange() {    uni.onBLECharacteristicValueChange(res => {        console.log(res)        let resHex = ab2hex(res.value)        console.log(resHex)        messageHex.value = resHex        let result = hexCharCodeToStr(resHex)        console.log(String(result))        message.value = String(result)    })}​// 【10】发送数据function send() {    // 向蓝牙设备发送一个0x00的16进制数据    let msg = "hello"        const buffer = new ArrayBuffer(msg.length)    const dataView = new DataView(buffer)    // dataView.setUint8(0, 0)        for (var i = 0; i < msg.length; i++) {      dataView.setUint8(i, msg.charAt(i).charCodeAt())    }        uni.writeBLECharacteristicValue({      deviceId: deviceId.value,      serviceId: serviceId.value,      characteristicId: characteristicId.value,      value: buffer,      success(res) {        console.log("writeBLECharacteristicValue success", res.errMsg)            uni.showToast({                title: "write指令发送成功"            })      },        fail(err) {            console.error(err)            uni.showToast({                title: "write指令发送失败",                icon: "error"            })        }    })}​// 【11】读取数据function read() {    uni.readBLECharacteristicValue({        deviceId: deviceId.value,        serviceId: serviceId.value,        characteristicId: characteristicId.value,        success(res) {            console.log(res)            uni.showToast({                title: "read指令发送成功"            })        },        fail(err) {            console.error(err)            uni.showToast({                title: "read指令发送失败",                icon: "error"            })        }    })} </script>​ 

本文转载于:

https://blog.csdn.net/pfourfire/article/details/124711068

如果对您有所帮助,欢迎您点个关注,我会定时更新技术文档,大家一起讨论学习,一起进步。

关键词: 发送数据 连接设备