百度360必应搜狗淘宝本站头条
当前位置:网站首页 > IT知识 > 正文

『uni-app、小程序』蓝牙连接、读写数据全过程 - 掘金

liuian 2025-01-17 12:18 45 浏览

本文简介

点赞 + 关注 + 收藏 = 学会了

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

本文使用 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. 发送指令:不管发送数据还是读取数据,都可以理解为向外发送指令

实现

上面整理出使用蓝牙传输数据的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 方法里可以传入一个对象,该对象接收几个参数,但初学的话我们只关注 success 和 fail。如果你的项目中硬件佬有提供 service 的 uuid 给你的话,你也可以在 services 里传入。其他参数可以查看官方文档的介绍。

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

代码示例

<template>
    <view>
        <scroll-view
            scroll-y
            class="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-view
            scroll-y
            class="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.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>
​
<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

在上两步我拿到了 设备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-view
            scroll-y
            class="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, // 设备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>
​
<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>

以上就是本文的完整代码。

相关推荐

进入三星官网(三星官网首页登录界面)

找一找我自己的输入密码,就可以登录官网去了。三星云端的官网登录入口为https://support.samsungcloud.com/登陆三星帐户后可查看三星云使用情况及三星云中的照片和文档等信息,选...

光速虚拟机怎么用(光速虚拟机怎么用流量使用)

部落冲突光速虚拟机是一款可以加速部落冲突游戏运行的工具。使用方法很简单,首先需要下载并安装光速虚拟机软件,然后打开软件并登录你的部落冲突账号。接下来,在光速虚拟机内部运行部落冲突游戏,就可以享受到游戏...

以太网无法连接到internet怎么解决
以太网无法连接到internet怎么解决

以太网不能连接到internet连接的原因,有可能是路由器的外网连接不通或者当前的主机无法获取有效IP地址导致的。这个时候需要具体问题具体分析,如果是前者的话,那么我们需要进入路由器中配置WAN上网条件。如果配置好了以后还是上不了网的话,那...

2025-12-20 01:05 liuian

cad注册表怎么彻底删除(cad2019注册表怎么彻底删除)

注:在操作注册表之前,请先备份注册表,以免操作失误造成系统故障,另外,本文中使用的操作方法适用于Windows操作系统。以下是详细的删除cad在注册表中的操作步骤:1.打开“运行”窗口,可以通过快...

内存不够用了怎么办(内存不够用了怎么办手机)

我们的手机在使用过程中,会产生各种各样的垃圾和缓存文件,这些东西占据了很大的内存空间,所以我们平时需要定期清理这些垃圾文件。安卓的手机基本都自带手机管家类型的APP,我们可以利用这类APP实现清理垃圾...

联想电脑怎么连接wifi(联想电脑怎么连接wifi步骤台式)
  • 联想电脑怎么连接wifi(联想电脑怎么连接wifi步骤台式)
  • 联想电脑怎么连接wifi(联想电脑怎么连接wifi步骤台式)
  • 联想电脑怎么连接wifi(联想电脑怎么连接wifi步骤台式)
  • 联想电脑怎么连接wifi(联想电脑怎么连接wifi步骤台式)
三星官方固件下载地址(三星官方固件下载网站)

请您参考:1.通过手机中“三星应用商店”或“Galaxy特色订制”搜索需要的软件并下载安装。2.通过手机浏览器搜索需要的软件下载安装(若是自带的浏览器,下载的安装包保存在我的文件-Download文件...

win10企业版和专业版有什么区别哪个好

就性能而言,Windows10企业版比专业版好。1.企业版的更新频率比专业版少,可以降低更新所需要的可用时间。2.如果你是企业,在管理、保护数据安全、部署和集中管理设备方面,企业版会比专业版更具优势...

51重装系统后怎么恢复原来的系统

方法/步骤分步阅读1/5在开始菜单中打开控制面板,打开控制面板之后,选择:备份和还原选项。2/5点击:打开系统还原,如果有账户控制则会有所提示,通过即可。3/5然后会开始还原文件和设置的向导,你只需要...

可能没有权限使用网络资源(可能没有权限使用网络资源 win10)

右击“我的电脑”→“属性”→“计算机名”,看该选项卡中有没有出现你的局域网工作组名称,如“workgroup”等。然后单击“网络ID”按钮,开始“网络标识向导”:单击“下一步”,选择“本机是商业网络...

怎么查看宽带账号和密码(怎么查宽带帐号和密码)
怎么查看宽带账号和密码(怎么查宽带帐号和密码)

宽带账号密码要记牢,重要的账号密码要记小本本上!下面我来具体讲讲获取方法。1.查看发票、合同、官方小卡片是否标明。以我们这个城市为例,发票上的用户号码就是宽带账号。047403100006就是宽带账号,密码是123456,密码错误就是身份证...

2025-12-19 21:05 liuian

预装系统的固态硬盘怎么安装

首先,安装系统有三个办法可以选择:一是在固态硬盘上全新安装系统;二是在PE系统下,将机械硬盘上的系统克隆到固态硬盘上。注意GPT格式的硬盘不能用这个方法。三是HDD安装。然后,主板设置中将固态硬盘设置...

如何还原已经被删除的文件(如何还原彻底删除的文件)

首先我们需要通过浏览器搜索互盾数据恢复软件,将这款软件下载到我们的电脑上1、下载好后运行互盾数据恢复软件,软件界面有六大功能,因为我们需要对回收站清空的数据进行恢复,所以点击界面的“误清空回收站”即...

mhdd硬盘修复教程(如何用mhdd修复硬盘)

eraser这个选项试试。mhdd修复效果不是很好哦打开MHDD的重映射功能,就是REMAP选为NO,然后再重新扫描就可以自动映射了1在DOS下运行MHDD,软件启动后按F4;2点一下上方向键,再...

手机真伪查询官网入口(荣耀手机真伪查询官网入口)

进入http://www.tenaa.com.cn/网站后,首先输入的是您的省份,城市,和许可证号.许可证号需要去掉电池,在手机的后面看一个贴条:进网许可;第一行就是许可证号;第二行是设备型号;第三行...