微信小程序 BLE 基础业务接口封装
写在前面:本文所述未必符合当前最新情形(包括蓝牙技术发展、微信小程序接口迭代等)。
微信小程序为蓝牙操作提供了很多接口,但在实际开发过程中,会发现隐藏了不少坑。目前主流蓝牙应用都是基于低功耗蓝牙(BLE)
的,本文介绍相关的几个基础接口,并对其进行封装,便于业务层调用。
蓝牙发展
在开发蓝牙应用程序之前,有必要对蓝牙这项技术做大致了解。
经典蓝牙
一种短距离无线通信标准,运行在 2.4GHz 频段,主要用于两个设备之间的数据传输。
一般将蓝牙 4.0 之前的版本称为经典蓝牙
,其传输速率在 1-3Mbps 之间。虽然有着不错的传输速率,但由于功耗较大,难以满足移动终端和物联网的需求,逐渐被更先进的版本所取代。
低功耗蓝牙(BLE)
蓝牙 4.0 引入了低功耗蓝牙(BLE)
技术,其最大数据吞吐量仅为1Mbps,但相对经典蓝牙,BLE 拥有超低的运行功耗和待机功耗。
BLE 的低功耗是如何做到的呢?主要是缩减广播通道数量(由经典蓝牙的 16-32个,缩减为 3 个)、缩短广播射频开启时间(由经典蓝牙的 22.5ms,减少到 0.6-1.2ms)、深度睡眠模式及针对低功耗场景优化了协议栈等,此处不赘述。
当前最新版本
当前大版本是蓝牙 5.0,传输速度达到了 24Mbps,是 4.2 版本的两倍,有效工作距离可达 300 米,是 4.2 版本的四倍。低功耗模式下的传输速度上限为 2Mbps,适合于影音级应用,如高清晰度音频解码协议的应用。
蓝牙特征值
GATT(Generic Attribute Profile)协议
定义了蓝牙设备之间的通信方式,其中单个服务(Service)
可以包含多个特征值(Characteristic)
,每个服务和特征值都有特定的 UUID 来唯一标识。特征值是蓝牙设备中用于存储和传输数据的基本单元,每个特征值都有其特定的属性和值。
属性协议(ATT)
定义数据的检索,允许设备暴露数据给其他设备,这些数据被称为属性(attribute)
。
通过属性可以设置特征值操作类型,如读取、写入、通知等,操作对象即为特征值的值(value)
。一个特征值可以同时拥有多种操作类型。
为了实现数据的传输,服务需要暴露两个主要的特征值:write
和notify 或 indication
。write 特征值用于接收数据,而 notify 特征值用于发送数据。这些特征值类型为 bytes,并且一次传输的数据长度可以根据不同的特征值类型有所不同。
小程序接口封装
需要知道的是,虽然蓝牙是开放协议,但由于苹果 IOS 系统的封闭设计,目前苹果设备无法与 Android 及其它平台设备通过蓝牙相连。
本文描述皆基于 Android 平台。
关键接口
使用蓝牙传输数据都会涉及以下步骤及接口:
- 激活设备蓝牙(如在手机上点按蓝牙图标);
wx.openBluetoothAdapter
:初始化小程序蓝牙模块;- 搜索外围设备
wx.onBluetoothDeviceFound
:监听搜索到新设备的事件;wx.startBluetoothDevicesDiscovery
:开始搜索附近设备;wx.stopBluetoothDevicesDiscovery
:找到待连的对手设备后停止搜索;
wx.createBLEConnection
:连接 BLE 设备;- 接收数据
wx.notifyBLECharacteristicValueChange
:为下一步骤做铺垫(注意:必须对手设备的特征支持 notify 或者 indicate 才可以成功调用);wx.onBLECharacteristicValueChange
:监听对手设备特征值变化事件,可以获得变化后的特征 value,如此数据就从对手设备传递过来了;
wx.writeBLECharacteristicValue
:向对手设备特征值中写入二进制数据(注意:必须对手设备的特征支持 write 才可以成功调用);wx.closeBLEConnection
:断开连接;wx.closeBluetoothAdapter
:关闭小程序蓝牙模块;- 关闭设备蓝牙。
坑及注意点(仅限于笔者基于开发过程使用到的机型观察记录,未必有普遍性):
- wx.onBluetoothDeviceFound 这个方法只能找到新的蓝牙设备,之前搜索过的在部分安卓机型上,不算做新的蓝牙设备,因此重新搜索不到。这种情况,要么重启小程序蓝牙模块或者重启小程序,或者使用
wx.getBluetoothDevices
获取在蓝牙模块生效期间所有搜索到的蓝牙设备。 - 连接未必能一次成功,需要多连几次。
- 每次连接最好能重启 BluetoothAdapter,否则在后续 wx.notifyBLECharacteristicValueChange 时容易报 10005-没有找到指定特征 错误。
- 若小程序在之前已有搜索过某个蓝牙设备,并成功建立连接,可直接传入之前搜索获取的 deviceId 直接尝试连接该设备,无需再次进行搜索操作。
- 系统与蓝牙设备会限制蓝牙 4.0 单次传输的数据大小,超过最大字节数后会发生写入错误,建议每次写入不超过 20 字节。
- 一旦过程中出现任何异常,就必须断开连接重连,否则后续会一直报 notifyblecharacteristicValuechange:fail: no characteristic 错误
主要代码
注:本文代码块为笔者临时盲敲,仅作参考。
定义一个工具对象
const ble = {}
由于可能会遇到的各类问题,我们先全局定义运行时异常枚举和 throw/handle 方法,免得后面遇到异常处理各写各的。
const ble = {
errors: {
OPEN_ADAPTER: '开启蓝牙模块异常',
CLOSE_ADAPTER: '关闭蓝牙模块异常',
CONNECT: '蓝牙连接异常',
NOTIFY_CHARACTERISTIC_VALUE_CHANGE: '注册特征值变化异常',
WRITE: '发送数据异常',
DISCONNECT: '断开蓝牙连接异常',
//...
},
_throwError(title, err) {
//... 可以考虑在这里调用 wx.closeBLEConnection
if (err) {
err.Title = title
throw err
}
throw new Error(title)
},
蓝牙连接。注意到这是个有限递归方法,且每次连接都先重启 BluetoothAdapter,原因请看上节。
/**
* @param {string} deviceId 设备号
* @param {int} tryCount 已尝试次数
*/
async connectBLE(deviceId, tryCount = 5) {
await wx.closeBluetoothAdapter().catch(err => { ble._throwError(this.errors.CLOSE_ADAPTER, err) })
await wx.openBluetoothAdapter().catch(err => { ble._throwError(this.errors.OPEN_ADAPTER, err) })
await wx.createBLEConnection({
deviceId: deviceId,
timeout: 5000
})
.catch(async err => {
if (err.errCode === -1) { //蓝牙已是连接状态
// continue work
} else {
console.log(`第${6 - tryCount}次蓝牙连接出错`, err.errCode, err.errMsg)
tryCount--
if (tryCount === 0) {
ble._throwError(this.errors.CONNECT, err)
} else {
await ble.connectBLE(deviceId, tryCount)
}
}
})
//蓝牙连接成功
},
连接成功后,可能需要监听对手设备,用于接收其传过来的数据。
async onDataReceive(deviceId, serviceId, characteristicId, callback) {
await wx.notifyBLECharacteristicValueChange({
deviceId: deviceId,
serviceId: serviceId,
characteristicId: characteristicId,
state: true
}).catch(err => { ble._throwError(this.errors.NOTIFY_CHARACTERISTIC_VALUE_CHANGE, err) })
wx.onBLECharacteristicValueChange(res => {
let data = new Uint8Array(res.value)
callback(data)
})
},
发送数据,须切片,每次发送不多于 20字节。此处增加了在固定时长内的重试机制。
/**
* @param {Uint8ClampedArray} data 待发送数据
* @param {boolean} holdConnWhenDone 发送完毕后是否保持连接
*/
async send(deviceId, serviceId, characteristicId, data, holdConnWhenDone = false) {
let idx = 0 //已传输字节数
let startTime = Date.now(),
duration = 800 //发送失败重试持续时间
while (idx < data.byteLength) {
await wx.writeBLECharacteristicValue({
deviceId: deviceId,
serviceId: serviceId,
characteristicId: characteristicId,
value: data.slice(idx, idx += 20).buffer
})
.then(_ => startTime = Date.now()) //成功则now重置
.catch(err => {
if (Date.now() - startTime >= duration) {
ble._throwError(this.errors.WRITE, err)
} else {
//重试
idx -= 20
}
})
}
if (!holdConnWhenDone)
await wx.closeBLEConnection({ deviceId: deviceId }).catch(err => { ble._throwError(this.errors.DISCONNECT, err) })
}
在实际项目中,可能需要在每次发送数据片之后得到对手设备响应后,根据响应决定重发(校验错误或响应超时等)、中止(设备繁忙)、还是接着发送下一个数据片。这种情况则需配合 onDataReceive 方法协同工作,向其传入合适的 callback 参数,此处不赘述。
微信小程序 BLE 基础业务接口封装的更多相关文章
- 微信小程序的Web API接口设计及常见接口实现
微信小程序给我们提供了一个很好的开发平台,可以用于展现各种数据和实现丰富的功能,通过小程序的请求Web API 平台获取JSON数据后,可以在小程序界面上进行数据的动态展示.在数据的关键 一环中,我们 ...
- 微信小程序高级基础
微信小程序高级基础 微信小程序的注册和服务器配置: 小程序是什么呢?小程序是一种不需要下载安装就可以使用的应用,它实现了应用"触手可及"的梦想,用户扫一扫或者搜一下就可以打开应用, ...
- 微信小程序开发基础
前言: 微信小程序开入入门,如果你有html+css+javascript的基础,那么你就很快地上手掌握的.下面提供微信小程序官方地址:https://developers.weixin.qq.com ...
- 微信小程序入门基础
微信小程序入门基础 视频教程(https://edu.csdn.net/course/detail/8456?pre_view=1) 第一章.认识小程序 1.工具的下载与安装 2.小程序代码构成 ...
- 记录一次用宝塔部署微信小程序Node.js后端接口代码的详细过程
一直忙着写毕设,上一次写博客还是元旦,大半年过去了.... 后面会不断分享各种新项目的源码与技术.欢迎关注一起学习哈! 记录一次部署微信小程序Node.js后端接口代码的详细过程,使用宝塔来部署. 我 ...
- 整合微信小程序的Web API接口层的架构设计
在我前面有很多篇随笔介绍了Web API 接口层的架构设计,以及对微信公众号.企业号.小程序等模块的分类划分.例如在<C#开发微信门户及应用(43)--微信各个项目模块的定义和相互关系>介 ...
- 微信小程序开发基础知识总结
微信小程序在无论在功能.文档及相关支持方面,都是优于前面几种微信账号类型,它提供了很多原生程序才有的接口,使得我们的小程序在很多方面突破H5页面应用的限制,更加接近原生程序的功能,因此微信小程序具有很 ...
- 微信小程序 API 基础
其实还有一些组件,没有提,因为那些组件跟 API 的功能差不多,API 可能比他会更好一点: 具体可见官方文档 基础: 判断接口是否可用:wx.canIUse(a) a 代表:接口名字 返回值:布尔 ...
- .NET开发微信小程序(基础配置)
1.微信小程序的必备Model public class WxConfig { /// <summary> /// 小程序的appId /// 登录小程序可以直接看到 /// </s ...
- 微信小程序内嵌业务域名内的网页
微信小程序在2017年11月左右开放了内嵌网页的功能,即新组件<web-view>.官方文档链接:https://mp.weixin.qq.com/debug/wxadoc/dev/com ...
随机推荐
- 【创龙全国产T3核心板】赋能工业领域新发展
在工业5.0时代浪潮持续推进并具备确定性的时代背景下,工业领域创新升级的需求日益增长,为满足各种工业环境下的应用需求,面向工业领域,创龙科技推出了基于全志T3处理器的元器件全国产化工业级核心板--SO ...
- nicegui 第一次
from nicegui import ui from ex4nicegui.reactive import rxui from ex4nicegui import to_ref,ref_comput ...
- Python 潮流周刊#59:Polars 1.0 发布了,PyCon US 2024 演讲视频也发布了(摘要)
本周刊由 Python猫 出品,精心筛选国内外的 250+ 信息源,为你挑选最值得分享的文章.教程.开源项目.软件工具.播客和视频.热门话题等内容.愿景:帮助所有读者精进 Python 技术,并增长职 ...
- Window版 MySQL可视化工具 Navicat 面安装免激活绿色版
网盘地址 链接:https://pan.baidu.com/s/1T0WyhGAFEt28GaU4wXhfrg 提取码:z4ww navicat15破解版 链接:https://pan.baidu.c ...
- yb课堂 ECMAScript 6常见语法快速入门 《三十一》
什么是ES 6 ECMAScript 6(简称ES6)是于2015年6月正式发布的JavaScript语言的标准,正式名为ECMAScript 2015(ES2015).它的目标是使用JavaScri ...
- 洛谷P1176
#include<iostream> #include<utility> using namespace std; typedef long long ll; #define ...
- 网易数帆实时数据湖 Arctic 的探索和实践
作者 | 蔡芳芳 采访嘉宾 | 马进 网易数帆平台开发专家 数据中台也要从离线为主走向实时化,湖仓一体是第一步. 数据从离线到实时是当前一个很大的趋势,但要建设实时数据.应用实时数据还面临两个难题.首 ...
- git分支学习笔记2-解决合并的冲突
来源:https://www.liuhaolin.com/git/115.html git中合并冲突是在不同的分支中同一个文件的内容不同导致的,如果进行合并就会冲突.文件可能是新增的文件,比如在两个分 ...
- 学习笔记--Java方法基础
Java方法基础 那么什么是方法呢? public class MethodTest01{ public static void main(String[] args){ // 需求1:编写程序计算 ...
- 记录一下实习的第一次线下面试的答辩经历,关于seata|sentinel,gateway与栈
面的一家小公司,他们准备做单体架构升级到微服务,所以问了我一些微服务相关的东西.回答的依托答辩,今天回想起来记录一下我造出来的笑话,正常的就不写在这里了. 首先我简历上大部分写的是熟悉,只有微服务写的 ...