Uniapp 开发 BLE

BLE

低功耗蓝牙(Bluetooth Low Energy,或称Bluetooth LE、BLE,旧商标Bluetooth Smart),用于医疗保健、运动健身、安防、工业控制、家庭娱乐等领域。在如今的物联网时代下大放异彩,扮演者重要一环,是无线通信的主流技术之一(常见的无线通信技术有NFC、GPRS、Zigbee、WiFi等),2021 年 7 月蓝牙技术联盟(Bluetooth SIG) 发布了蓝牙 5.3 版本,也是截止目前的最新版本。
蓝牙(BT)最早诞生于1999年,第一代蓝牙是单工传输的、通信易受干扰,难以区分主从设备、传输速率才几百kbps,一路发展,直到 蓝牙4.0 版本,才有了低功耗蓝牙BLE的诞生。

  • 经典蓝牙
    经典蓝牙泛指支持蓝牙协议4.0以下的蓝牙,经典蓝牙一般用于连续流式传输音频和数据量比较大的传输,例如音乐、语音、打印机等。
  • 低功耗蓝牙
    低功耗蓝牙ble指支持蓝牙协议4.0或更高的版本,它不向后兼容4.0之前的经典蓝牙协议,主打低功耗(使用一个纽扣电池最起码都能工作好几个月),低延迟(几毫秒级别的响应);应用于实时性要求比较高,但是低速率,低功耗的场景,如鼠标键盘、智能家居、智能穿戴这类不需要大数据量交互的场景中,非常适合物联网应用。

前序

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

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

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

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

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

花了几块钱巨款买回来的蓝牙学习套装~

环境说明

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

思路

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

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

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

大致思路

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

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

从步骤的实现目标的角度来说,大概思路如下:

  1. 搜索 蓝牙设备
  2. 获取 设备ID, 连接 设备
  3. 获取 蓝牙设备 的所有 服务
  4. 获取服务 所有的 特征值
  5. 根据 特征值 进行 发送指令 操作

实现

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

初始化阶段

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

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

错误代码可以查阅 《错误码文档》

代码示例

<template><view><button @click="initBlue">初始化蓝牙</button></view>
</template><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步:

  1. 开启搜寻功能:uni.startBluetoothDevicesDiscovery
  2. 监听搜寻到新设备:uni.onBluetoothDeviceFound

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

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

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

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

代码示例

<template><view><scroll-viewscroll-yclass="box"><view class="item" v-for="item in blueDeviceList"><view><text>id: {{ item.deviceId }}</text>    </view><view><text>name: {{ item.name }}</text>    </view></view></scroll-view><button @click="initBlue">初始化蓝牙</button><button @click="discovery">搜索附近蓝牙设备</button></view>
</template><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><style>
.box {width: 100%;height: 400rpx;box-sizing: border-box;margin-bottom: 20rpx;border: 2px solid dodgerblue;
}
.item {box-sizing: border-box;padding: 10rpx;border-bottom: 1px solid #ccc;
}
button {margin-bottom: 20rpx;
}
</style>

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

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

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

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

连接目标设备

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

流程如下:

  1. 获取设备ID:根据 uni.onBluetoothDeviceFound 回调,拿到设备ID
  2. 连接设备:使用设备ID进行连接 uni.createBLEConnection
  3. 停止搜索:uni.stopBluetoothDevicesDiscovery

我给每条搜索到的蓝牙结果添加一个 click 事件,会向目标设备发送连接请求。

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

代码示例

<template><view><scroll-viewscroll-yclass="box"><view class="item" v-for="item in blueDeviceList" @click="connect(item)"><view><text>id: {{ item.deviceId }}</text>    </view><view><text>name: {{ item.name }}</text>    </view></view></scroll-view><button @click="initBlue">初始化蓝牙</button><button @click="discovery">搜索附近蓝牙设备</button></view>
</template><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])
}// 蓝牙设备的id
const deviceId = ref('')// 【4】连接设备
function connect(data) {console.log(data)deviceId.value = data.deviceIduni.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><style>
.box {width: 100%;height: 400rpx;box-sizing: border-box;margin-bottom: 20rpx;border: 2px solid dodgerblue;
}
.item {box-sizing: border-box;padding: 10rpx;border-bottom: 1px solid #ccc;
}
button {margin-bottom: 20rpx;
}
</style>

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

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

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

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

连接成功后,设备也亮起了绿灯。

监听

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

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

步骤如下:

  1. 获取蓝牙设备服务:uni.getBLEDeviceServices
  2. 获取特征值:uni.getBLEDeviceCharacteristics
  3. 开启消息监听:uni.notifyBLECharacteristicValueChange
  4. 接收消息监听传来的数据:uni.onBLECharacteristicValueChange

正常情况下,硬件佬会提前把蓝牙设备的指定服务还有特征值告诉你。

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

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

第一步,获取蓝牙服务

<template><view><!-- 省略上一步的代码 --><button @click="getServices">获取蓝牙服务</button></view>
</template><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

在上两步我拿到了 设备IDB4:10:7B:C4:83:14服务ID0000FFE0-0000-1000-8000-00805F9B34FB

<template><view><!-- 省略前面几步代码 --><button @click="getCharacteristics">获取特征值</button></view>
</template><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 方法触发监听到的消息。

<template><view><!-- 省略前面几步代码 --><button @click="notify">开启消息监听</button></view>
</template><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

代码示例

<template><view><!-- 省略前面几步代码 --><button @click="send">发送数据</button></view>
</template><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.writeBLECharacteristicValuesuccess ,证明你已经把数据向外成功发送了,但不代表设备一定就收到了。

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

uni.readBLECharacteristicValue

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

代码示例

<template><view><!-- 省略前面几步代码 --><button @click="read">读取数据</button></view>
</template><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 只负责发送读取的请求,并且里面的 successfail 只是返回你本次发送请求的动作是否成功,至于对面的蓝牙设备有没有收到这个指令你是不清楚的。

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

完整代码

<template><view><scroll-viewscroll-yclass="box"><view class="item" v-for="item in blueDeviceList" @click="connect(item)"><view><text>id: {{ item.deviceId }}</text>    </view><view><text>name: {{ item.name }}</text>    </view></view></scroll-view><button @click="initBlue">1 初始化蓝牙</button><button @click="discovery">2 搜索附近蓝牙设备</button><button @click="getServices">3 获取蓝牙服务</button><button @click="getCharacteristics">4 获取特征值</button><button @click="notify">5 开启消息监听</button><button @click="send">6 发送数据</button><button @click="read">7 读取数据</button><view class="msg_x"><view class="msg_txt">监听到的内容:{{ message }}</view><view class="msg_hex">监听到的内容(十六进制):{{ messageHex }}</view>    </view>    </view>
</template><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])
}// 蓝牙设备的id
const 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,开发中需要问硬件佬获取该id
const 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, // 设备idserviceId: 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 = resHexlet 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><style>
.box {width: 98%;height: 400rpx;box-sizing: border-box;margin: 0 auto 20rpx;border: 2px solid dodgerblue;
}
.item {box-sizing: border-box;padding: 10rpx;border-bottom: 1px solid #ccc;
}
button {margin-bottom: 20rpx;
}.msg_x {border: 2px solid seagreen;width: 98%;margin: 10rpx auto;box-sizing: border-box;padding: 20rpx;
}.msg_x .msg_txt {margin-bottom: 20rpx;
}
</style>

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

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

相关文章

PIC单片机项目(8)——基于PIC16F877A的温度光照检测装置的protues仿真

1.功能设计 使用PIC16F877A单片机&#xff0c;进行温度检测、光照检测。温度使用的是DS18B20&#xff0c;光照检测直接利用的AD转换。 光照太暗就开灯&#xff0c;温度太高就开风扇。温度阈值和光照阈值都实时显示在LCD1602屏幕上面。 完成了protues仿真。文件里面包含代码和仿…

Liteos移植_STM32_HAL库

0 开发环境 STM32CubeMX(HAL库)keil 5正点原子探索者STM32F4ZET6LiteOS-develop分支 1 STM32CubeMX创建工程 如果有自己的工程&#xff0c;直接从LiteOS源码获取开始 关于STM32CubeMX的安装&#xff0c;看我另一篇博客STM32CubeMX安装 工程配置 创建新工程 选择芯片【STM32F…

第一次记录QPSK,BSPK,MPSK,QAM—MATLAB实现

最近有偶然的机会学习了一次QPSK防止以后忘记又得找资料&#xff0c;这里就详细的记录一下 基于 QPSK 的通信系统如图 1 所示&#xff0c;QPSK 调制是目前最常用的一种卫星数字和数 字集群信号调制方式&#xff0c;它具有较高的频谱利用率、较强的抗干扰性、在电路上实现也较为…

蓝牙物联网智能家居安防检测系统解决方案

随着科学技术的发展&#xff0c;我们的生活方式正在进行着翻天覆地的变化。互联网技术的实现推动了物联网新模式的出现改变着我们生活&#xff0c;使我们的社会生产生活变得更加的便利与人性化。借此现如今我们的生活方式更是向智能家居方向所发展&#xff0c;这一课题正在被相…

电脑组件整理(持续更新...)

1、*芯片 CPU(承担着负责的运算)&#xff1b; 量大阵营&#xff1a; AMD &#xff5c; Intel i5 12400F 6核12线 7nm -- 1189元 r7 5700x 8核16线 7 nm -- 1500元&#xff1b; ARM采用的是RISC精简指令集计算、 主要应用于移动终端之中&#xff0c;类如手机&#xff0c…

了解树和学习二叉树

1.树 1.1 概念 树是一种 非线性 的数据结构&#xff0c;它是由 n &#xff08; n>0 &#xff09;个有限结点组成一个具有层次关系的集合。 把它叫做树是因为它看 起来像一棵倒挂的树&#xff0c;也就是说它是根朝上&#xff0c;而叶朝下的 。 注意&#xff1a;树形结构中…

Kafka核心参数(带完善)

客户端 api Kafka提供了以下两套客户端API HighLevel(重点)LowLevel HighLevel API封装了kafka的运行细节&#xff0c;使用起来比较简单&#xff0c;是企业开发过程中最常用的客户端API。 而LowLevel API则需要客户端自己管理Kafka的运行细节&#xff0c;Partition&#x…

Linux-Keepalived(VRRP协议)高可用集群搭建

Linux-Keepalived&#xff08;VRRP协议&#xff09;高可用集群搭建 一、VRRP简介1.1 什么是VRRP&#xff1f;1.2 keepalived是什么&#xff1f;1.3 keepalived工作原理 二、实操配置过程2.1 试验模型2.2. Keepalived监控和维护VRRP集群的步骤2.2.1 安装keepalived2.2.2 配置kee…

sql_lab之sqli中的head头注入,less18

报错注入中的head注入&#xff08;less-18&#xff09; 1.输入用户名和密码123 123显示登录错误 2.输入用户名和密码123’ 123显示登录错误 则证明不是普通报错注入&#xff0c;因为有用户名和密码框&#xff0c;如果不是普通报错注入则尝试head注入 3.用burp进行爆破&#x…

【已解决】在使用frp内网穿透访问VUE项目提示:Invalid Host/Origin header 解决方案

项目配置 在使用frp作为内网穿透的时候&#xff0c;配置了多端口穿透(也是第一次配置frp多端口)&#xff0c;端口配置如下&#xff1a; 8079&#xff1a;vue项目的管理系统 8080&#xff1a;vue项目的前台系统 8082&#xff1a;普通的web项目 更高frp相关问题 &#x1f4…

4.3 C++对象模型和this指针

4.3 C对象模型和this指针 4.3.1 成员变量和成员函数分开存储 在C中&#xff0c;类内的成员变量和成员函数分开存储 只有非静态成员变量才属于类的对象上 #include <iostream>class Person { public:Person() {mA 0;} //非静态成员变量占对象空间int mA;//静态成员变量…

数据预处理:多重共线性_检测和解决办法

文章目录 1.多重共线性简介&#xff08;Collinearity and Multicollinearity&#xff09;1.1 多重共线性的后果1.2 处理多重共线性问题的方法 2. 设置2.1 导入库2.2 数据集特征波士顿房价BMI 数据集 2.3 导入数据 3. 相关矩阵3.1 聚类图 4. 方差膨胀因子4.1 两种多重共线性4.2 …

外卖托管运营专家邦火策划怎么样,为您的餐厅带来了什么不同?

在当今激烈竞争的餐饮市场&#xff0c;外卖托管运营正逐渐成为许多餐厅提升业绩的有效手段。邦火策划以其专业的服务和独特的策略&#xff0c;成为外卖托管运营领域的专家。让我们一同探究&#xff0c;选择邦火策划为您的餐厅带来了怎样的不同。 在邦火策划的引领下&#xff0…

【UML】第10篇 类图(属性、操作和接口)(2/3)

目录 3.3 类的属性&#xff08;Attribute&#xff09; 3.3.1 可见性&#xff08;Visibility&#xff09; 3.3.2 属性的名称 3.3.3 数据类型 3.3.4 初始值 3.3.5 属性字符串 3.4 类的操作&#xff08;Operations&#xff09; 3.4.1 参数表 3.4.2 返回类型 3.5 类的职责…

基于JavaWeb的个人健康信息管理系统论文

摘 要 现代经济快节奏发展以及不断完善升级的信息化技术&#xff0c;让传统数据信息的管理升级为软件存储&#xff0c;归纳&#xff0c;集中处理数据信息的管理方式。本个人健康信息管理系统就是在这样的大环境下诞生&#xff0c;其可以帮助管理者在短时间内处理完毕庞大的数据…

data数据响应式

data数据响应式 所有在实例上挂载的属性&#xff0c;都可以在视图中直接使用 data中的数据&#xff0c;是经过“数据劫持”的&#xff0c;是“响应式数据” 响应式&#xff1a;修改数据&#xff0c;视图会自动更新 MV原理&#xff1a;其中一条线的原理&#xff0c;data响应式的…

Nessus详细安装-windows (保姆级教程)

Nessus描述 Nessus 是一款广泛使用的网络漏洞扫描工具。它由 Tenable Network Security 公司开发&#xff0c;旨在帮助组织评估其计算机系统和网络的安全性。 Nessus 可以执行自动化的漏洞扫描&#xff0c;通过扫描目标系统、识别和评估可能存在的安全漏洞和弱点。它可以检测…

DRF从入门到精通三(反序列化数据校验源码分析、断言Assert、DRF之请求、响应)

文章目录 一、反序列化数据校验源码分析二、断言Assert三、DRF之请求、响应Request类和Response类请求中的Request 能够解析前端传入的编码格式响应中的Response能够响应的编码格式 一、反序列化数据校验源码分析 反序列化数据校验&#xff0c;校验顺序为&#xff1a;先校验字段…

Go后端开发 -- Golang的语言特性

Go后端开发 – Golang的语言特性 文章目录 Go后端开发 -- Golang的语言特性一、Golang的优势1.部署极其简单&#xff1a;2.静态语言3.语言层面的并发4.强大的标准库5.简单易学6.运行效率对比 二、Golang的适用领域1.应用领域2.明星产品 三、Golang的不足 一、Golang的优势 1.部…

共享购:消费前沿的领导者

在当今这个信息化、互联网高速发展的时代&#xff0c;商业模式也在不断地创新和变革。共享购模式作为一种新型的商业模式&#xff0c;正逐渐受到广泛的关注和追捧。本文将深入探讨共享购模式的核心理念、优势以及如何应用在实际商业场景中&#xff0c;为读者揭示这一模式的巨大…