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
来看,蓝牙接收数据会显得更加**“异步”**。
大致思路
使用蓝牙进行数据传输的大概思路如下:
- 初始化:打开蓝牙模块
- 搜寻:检测附近存在的设备
- 连接:找到目标设备进行
- 监听:开启监听功能,接收其他设备传过来的数据
- 发送指令:不管发送数据还是读取数据,都可以理解为向外发送指令
从步骤的实现目标的角度来说,大概思路如下:
- 搜索
蓝牙设备
- 获取
设备ID
, 连接 设备 - 获取 蓝牙设备 的所有
服务
- 获取服务 所有的
特征值
- 根据 特征值 进行
发送指令
操作
实现
上面整理出使用蓝牙传输数据的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步:
- 开启搜寻功能:uni.startBluetoothDevicesDiscovery
- 监听搜寻到新设备:uni.onBluetoothDeviceFound
开发蓝牙相关功能时,操作逻辑更像是推送,所以“开启搜索”和“监听新设备”是分开操作的。
uni.startBluetoothDevicesDiscovery
可以让设备开始搜索附近蓝牙设备,但这个方法比较耗费系统资源,建议在连接到设备之后就使用 uni.stopBluetoothDevicesDiscovery
停止继续搜索。
uni.startBluetoothDevicesDiscovery
方法里可以传入一个对象,该对象接收几个参数,但初学的话我们只关注 success
和 fail
。如果你的项目中硬件佬有提供 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
就能完成。但根据文档提示,我们连接后还需要关闭 “搜索附近设备” 的功能,这个很好理解,既然找到了,再继续找就是浪费资源。
流程如下:
- 获取设备ID:根据 uni.onBluetoothDeviceFound 回调,拿到设备ID
- 连接设备:使用设备ID进行连接 uni.createBLEConnection
- 停止搜索: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"}
连接成功后,设备也亮起了绿灯。
监听
在连接完设备后,就要先开启监听数据的功能。这样才能接收到发送读写指令后设备给你回调的信息。
要开启监听,首先需要知道蓝牙设备提供了那些服务,然后通过服务获取特征值,特征值会告诉你哪个可读,哪个可写。最后根据特征值进行消息监听。
步骤如下:
- 获取蓝牙设备服务:uni.getBLEDeviceServices
- 获取特征值:uni.getBLEDeviceCharacteristics
- 开启消息监听:uni.notifyBLECharacteristicValueChange
- 接收消息监听传来的数据: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。
在上两步我拿到了 设备ID 为 B4:10:7B:C4:83:14
,服务ID 为 0000FFE0-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.writeBLECharacteristicValue
走 success
,证明你已经把数据向外成功发送了,但不代表设备一定就收到了。
通常设备收到你发送过去的信息,会返回一条消息给你,而这个回调消息会在 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
只负责发送读取的请求,并且里面的 success
和 fail
只是返回你本次发送请求的动作是否成功,至于对面的蓝牙设备有没有收到这个指令你是不清楚的。
最后需要通过 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>
相关文章:
Uniapp 开发 BLE
BLE 低功耗蓝牙(Bluetooth Low Energy,或称Bluetooth LE、BLE,旧商标Bluetooth Smart),用于医疗保健、运动健身、安防、工业控制、家庭娱乐等领域。在如今的物联网时代下大放异彩,扮演者重要一环ÿ…...
c语言排序算法
C语言代码示例: 冒泡排序(Bubble Sort): void bubbleSort(int arr[], int n) {for (int i 0; i < n-1; i) {for (int j 0; j < n-i-1; j) {if (arr[j] > arr[j1]) {int temp arr[j];arr[j] arr[j1];arr[j1] temp;…...
【机器学习】模式识别
1 概述 模式识别,简单来讲,就是分类问题。 模式识别应用:医学影像分析、人脸识别、车牌识别、遥感图像 2 模式分类器 分类器的分类:线性分类器、非线性分类器、最近邻分类器 2.1 分类器的训练(学习)过…...
【Prometheus|报错】Out of bounds
【背景】进入Prometheus地址的9090端口,pushgateway(0/1)error : out of bounds 【排查分析】 1、out of bounds报错,是由于Prometheus向tsdb存数据出错,与最新存数据的时间序列有问题,有可能当前时间与最…...
【音视频】Mesh、Mcu、SFU三种框架的总结
目录 三种网络场景介绍 【Mesh】 【MCU】(MultiPoint Control Unit) 【SFU】(Selective Forwarding Unit) 三种网络架构的优缺点 Mesh架构 MCU架构(MultiPoint Control Unit) SFU架构(Selective Forwarding Unit) 总结 参考文章 三种网络场景介绍 【Mesh】 Mesh架构…...
高级算法设计与分析(四) -- 贪心算法
系列文章目录 高级算法设计与分析(一) -- 算法引论 高级算法设计与分析(二) -- 递归与分治策略 高级算法设计与分析(三) -- 动态规划 高级算法设计与分析(四) -- 贪心算法 高级…...
MATLAB - 机器人逆运动学设计器(Inverse Kinematics Designer APP)
系列文章目录 前言 一、简介 通过逆运动学设计器,您可以为 URDF 机器人模型设计逆运动学求解器。您可以调整逆运动学求解器并添加约束条件,以实现所需的行为。使用该程序,您可以 从 URDF 文件或 MATLAB 工作区导入 URDF 机器人模型。调整逆…...
使用OpenCV DNN模块进行人脸检测
内容的一部分来源于贾志刚的《opencv4应用开发、入门、进阶与工程化实践》。这本书我大概看了一下,也就后面几章比较感兴趣,但是内容很少,并没有想像的那种充实。不过学习还是要学习的。 在实际工程项目中,并不是说我们将神经网络…...
C#中使用OpenCV的常用函数
以下是一些C#中使用OpenCV的常用函数例子: 1. 加载图像: using OpenCvSharp;Mat image Cv2.ImRead("path_to_your_image.jpg", ImreadModes.Color); 2. 显示图像: Cv2.NamedWindow("Image Window", WindowFlags.Nor…...
使用Swift Package Manager (SPM)实现xcframework分发
Swift Package Manager (SPM) 是苹果官方提供的用于管理 Swift 项目的依赖关系和构建过程的工具。它是一个集成在 Swift 编程语言中的包管理器,用于解决在开发过程中管理和构建包依赖项的需求。 1、上传xcframework.zip到服务端 压缩xcframeworks成一个zip包&…...
非阻塞 IO(NIO)
文章目录 非阻塞 IO(NIO)模型驱动程序应用程序模块使用 非阻塞 IO(NIO) 上一节中 https://blog.csdn.net/tyustli/article/details/135140523,使用等待队列头实现了阻塞 IO 程序使用时,阻塞 IO 和非阻塞 IO 的区别在于文件打开的时候是否使用了 O_NONB…...
Android应用-flutter使用Positioned将控件定位到底部中间
文章目录 场景描述示例解释 场景描述 要将Positioned定位到屏幕底部中间的位置,你可以使用MediaQuery来获取屏幕的高度,然后设置Positioned的bottom属性和left或right属性,一般我们left和right都会设置一个值让控制置于合适的位置࿰…...
Django 简单图书管理系统
一、图书需求 1. 书籍book_index.html中有超链接:查看所有的书籍列表book_list.html页面 2. 书籍book_list.html中显示所有的书名,有超链接:查看本书籍详情book_detail.html(通过书籍ID)页面 3. 书籍book_detail.html中书的作者和出版社&…...
C++内存管理和模板初阶
C/C内存分布 请看代码: int globalVar 1; static int staticGlobalVar 1; void Test() {static int staticVar 1;int localVar 1;int num1[10] { 1, 2, 3, 4 };char char2[] "abcd";const char* pChar3 "abcd";int* ptr1 (int*)mallo…...
QtRO(Qt Remote Objects)分布式对象远程通信
一、什么是QtRO Qt Remote Objects(QRO)是Qt提供的一种用于实现远程对象通信的机制。 QtRO支持两种类型的通信:RPC(远程过程调用)和LPC(本地进程通信)。 RPC(远程过程调用…...
【K8s】1# 使用kuboard-spray安装K8s集群
文章目录 搭建k8s集群1.推荐配置1.1.服务器配置1.2.软件版本 2.使用Kuboard-Spray安装k8s集群2.1.配置要求2.2.操作系统兼容性2.3.安装 Kuboard-Spray2.4.加载离线资源包2.5.规划并安装集群2.6.安装成功2.7.访问集群 3.涉及的命令3.1.linux 4.问题汇总Q1:启动离线集…...
leetCode算法—12. 整数转罗马数字
12. 整数转罗马数字 难度:中等 ** 罗马数字包含以下七种字符: I, V, X, L,C,D 和 M。 字符 数值 I 1 V 5 X 10 L 50 C 100 D 500 M 1000 例如, 罗马数字 2 写做 II ,即…...
使用OpenCV4实现工业缺陷检测的六种方法
目录 1 机器视觉2 缺陷检测3 工业上常见缺陷检测方法 1 机器视觉 机器视觉是使用各种工业相机,结合传感器跟电气信号实现替代传统人工,完成对象识别、计数、测量、缺陷检测、引导定位与抓取等任务。其中工业品的缺陷检测极大的依赖人工完成,…...
Excel 获取当前行的行数
ROW() 获取当前行 ROW()1 获取当前行然后支持二次开发...
R语言【stringr】——str_detect 检测是否存在字符串的匹配项
Package stringr version 1.5.1 str_detect(string, pattern, negate FALSE) 参数【string】:输入向量。既可以是字符向量,也可以是强制作为一个字符向量。 参数【pattern】:要寻找的模式。默认解释为正则表达式,如 vignette(&…...
【SpringMVC】SpringMVC的请求与响应
文章目录 0. Tomcat环境的配置1. PostMan工具介绍创建WorkSpace建立新的请求 2. 请求映射路径案例结构与代码案例结构案例代码 案例存在问题解决方案方法方法升级版——配置请求路径前缀注解总结 3. Get请求与Post请求案例结构与案例代码案例结构案例代码 Get请求Post请求接收中…...
Spring Boot3通过GraalVM生成exe执行文件
一、安装GraalVM 1、官网:https://www.graalvm.org/downloads/ 2、配置环境变量 2.1、环境变量必须使用JAVA_HOME,否则会出现问题 2.2、在系统变量配置Path,%JAVA_HOME%\bin,注意必须放在顶部第一位 2.3、配置jdk的环境变量,在P…...
【Amazon 实验②】使用缓存策略及源请求策略,用于控制边缘缓存的行为及回源行为
文章目录 1. 了解缓存策略和源请求策略1.1 使用缓存键和缓存策略 实验:使用CloudFront缓存策略和缓存键控制缓存行为 接上一篇文章【Amazon 实验①】使用 Amazon CloudFront加速Web内容分发,我们现在了解和配置如何使用缓存策略及源请求策略,…...
达梦数据对比工具的部署与使用
1、拷贝达梦软件bin目录到Oracle服务器(root用户) 压缩Linux rh6 x86版本的达梦数据库bin目录,例如压缩文件为dmbin.tar.gz,将文件拷贝到Oracle服务器指定目录并解压(如:/home/oracle/dmbin)&a…...
TLC2543(12位A/D转换器)实现将输入的模拟电压显示到数码管上
代码: #include <reg51.h> #define uchar unsigned char #define uint unsigned int// 数码管0-9 unsigned char seg[] {0x3F, 0x06, 0x5B, 0x4F, 0x66, 0x6D, 0x7D, 0x07, 0x7F, 0x6F}; sbit SDO P1^0; sbit SDI P1^1; sbit CS P1^2; sbit CLK P1^3; s…...
npm的使用技巧
以下是一些NPM(Node Package Manager)的使用技巧: 1. **获取帮助**: - 使用 npm help 或者 npm <command> --help 可以获取关于特定命令的帮助信息。 2. **命令自动完成**: - 在 Bash、Zsh 等 shell 中&…...
MySQL 5.6的新特性
MySQL 5.6是一个主要的版本发布,它在性能、可伸缩性、可靠性和可用性方面引入了多项重要改进和新特性。它在2013年发布,相比于它的前身MySQL 5.5,MySQL 5.6带来了以下关键升级: 优化的InnoDB存储引擎:MySQL 5.6中的Inn…...
大模型重构云计算:AI原生或将改变格局
摘要:随着AI技术的快速发展,大模型正逐渐改变云计算的格局。本文将深入探讨大模型如何重构云计算,并分析其对云计算的影响。 一、开篇引言 近年来,人工智能技术的飞速发展,特别是大模型的崛起,正在对云计算…...
一文讲清什么是TypeScript装饰器以及如何使用TypeScript装饰器
TypeScript 装饰器是什么? 装饰器(Decorator)是TypeScript提供的一个高级语法,它类似于一种特殊类型的声明,可以附加到类声明,方法,访问符,属性或参数上。装饰器主要以函数的形式出…...
恶意软件样本行为分析——Process Monitor和Wireshark
1.1 实验名称 恶意软件样本行为分析 1.2 实验目的 1) 熟悉 Process Monitor 的使用 2) 熟悉抓包工具 Wireshark 的使用 3) VMware 的熟悉和使用 4) 灰鸽子木马的行为分析 1.3 实验步骤及内容 第一阶段:熟悉 Process Monitor 的使用 利用 Process …...
wordpress divi 教程/在seo优化中
自己写的程序CPU占用率过高,无法锁定原因时,可以用VS2013帮忙检测 1. 打开VS 性能分析 2. 启动项目进行检测 3. 选择CPU采样 完成 4. 分析一段时间 然后停止分析 5. 选择显示代码 6. 阿萨德 可以查看具体函数的占用比了 原文:http://blog.csdn.n…...
江津网站建设/神马站长平台
题意: 用 a b c 三个字符构造一个长度为 N 的串,保证串中不出现长度大于等于 3 的回文串 并且 尽可能使字符 c 出现的次数最少。 思路: 串用 aabb 构造即可满足题意 例:长度为 3 的串为 aab 5 aabba 8 aabbaabb …...
律师事务所网站设计/营销型网站建设价格
Python 近两年一直霸占编程语言排行榜 Top3,火热程度有目共睹。这也让刚入行的程序员,甚至 BATJ 的技术大牛,都意识到 Python 对于一个程序员职业发展的重要性,将其作为第一/第二开发语言去学习。虽然 Python 以简单易学著称&…...
炫酷业务网站/网络营销属于什么专业类型
原文链接:http://tecdat.cn/?p19839tecdat.cn机器学习算法可用于找到最佳值来交易您的指标。相对强弱指标(RSI)是最常见的技术指标之一。它用于识别超卖和超买情况。传统上,交易者希望RSI值超过70代表超买市场状况,而低于30则代表超卖市场…...
长沙市做网站的/我想做个网站怎么做
1, 昨天主要进行了主界面的设计,问题是当从更深层次的界面返回上一级界面时,上一级界面不出现在屏幕中央,而是出现在了靠左上方的位置。今天进行游戏主代码的编写。 2,完善界面设置过程中遇到问题:每次启动…...
惠州网站建设哪里有/北京企业推广
1.实时性:保证消息实时触达是互动场景的必备能力。 对于一个实时消息系统,“实时”二字很好地表达了这个系统的基本要求。通过微信和你的好友聊天,结果等半天对方才收到,基本上也没有意愿聊了;直播场景下,…...