0%

微信小程序蓝牙BLE开发——API及流程介绍(一)

迟来的更新。从4月份以来项目中断续在对接好几个共享产品,关于蓝牙BLE设备,通过蓝牙设备之间通信进行使用产品。
开发中也遇到不少问题哈,后面抽时间续篇。写得不好,请各位大神多多指教。

此篇主要介绍一些API操作及一些返回数据结构, 项目已上线。后面抽时间上demo

关于字节

字节(Byte):计算机信息技术用于计量存储容量的一种计量单位,作为一个单位来处理的一个二进制数字串。

其中下发指令或处理数据时都可以应用到

  • 1B(byte,字节)= 8 bit(比特), 相当于一个字符
  • 一个字节能表示的最大的整数就是255
  • 例如: 数据为5d000001be5d理解为6个字节(6B)

流程图

BLE执行大致流程,为了节省空间,画的比较乱哈。

在这里插入图片描述

使用步骤

微信小程序低功耗蓝牙API

微信小程序官方_蓝牙说明

初始化数据

根据需求定义,个人在一个项目中开发好几个产品,同时对接不同蓝牙协议供应商,有些变量放全局,根据不同供应商来操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
var serviceUUID = [] //主 service 的 uuid 列表
var writeUUID = ""; //写读 UUID
var notifyUUID = ""; //notify UUID
var filterServiceUUID = ""; //过滤获取到的服务uuid(有些会返回多条数据)
var filterDeviceName = ""; //设备名称

var macAddress = ""; //保存得到mac地址
var flagFromTypes = ''; //来源类型
var _discoveryStarted = false;
var deviceId = ''; //用于区分设备的 id

var _deviceId = '';
var _serviceId = '';
var _characteristicId = '';
var status = false; //当前状态
var action_type = ''; //操作类型
var code = -1;
var isnotExist = true

给变量赋值

设备相关服务通常蓝牙协议文档上有说明,ReadWriteNotify。如果没有说明可通过搜索设备看到

某些供应商读写通过统一用一个服务

1
2
3
4
5
6
serviceUUID[0] = "0000*E0-00*0-*0*0-*0*0-00**5F9**4*B"; //主 service 的 uuid 列表
writeUUID = "00*0**E2-00*0-*0*0-*0*0-00**5F9**4*B"; //写读 UUID
notifyUUID = "00*0**E1-00*0-*0*0-*0*0-00**5F9**4*B"; //notify UUID
filterServiceUUID = "*E0";
filterDeviceName = getNameMac(macAddress, 6, 'abc_'); //设备名称

1. 初始化蓝牙模块

1
wx.openBluetoothAdapter(Object obj)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
function initBle(fromMac, flagTypes, currentSerial) {
//断开连接【每次初始化先断开连接】
closeBLEConnection();

// macAddress = clearSymbol(fromMac);
macAddress = fromMac; //保存mac
flagFromTypes = flagTypes //类型来源
currentSerialVal = currentSerial //当前操作序号


wx.openBluetoothAdapter({
success: (res) => {
console.log('openBluetoothAdapter 初始化蓝牙模块是否成功:', res)

// 监听寻找新设备事件
onBluetoothDeviceFound();

//开始搜寻附近的蓝牙外围设备
startBluetoothDevicesDiscovery();
},
fail: (res) => {
console.log('初始化蓝牙失败', res);
//自行处理【可弹窗提示用户开启蓝牙】,这通过回调处理
asddErrorCallback(res.errCode, "");

//监听蓝牙适配器状态变化事件【根据需求是否执行】
// wx.onBluetoothAdapterStateChange(function (res) {
// console.log('蓝牙适配器状态更改结果: ', res)
// if (res.available) {
// console.log('蓝牙可用,搜索设备:--》 ')
// onBluetoothDeviceFound();
// startBluetoothDevicesDiscovery();
// }
// })
}
})
}

2. 监听寻找新设备事件

1
wx.onBluetoothDeviceFound(function callback)

广播数据: 可以得到当前蓝牙设备的相关数据,另外,如果设备有返回其他数据时在advertisData数据段中得到,【有些供应商是没有返回的】

设备返回数据效果图

下图是两家供应商设备返回的数据, 左图advertisData返回8个字节数据【数据需转换】。右图则没有。同时可以看到返回name格式也是不一样的【自定义】

在这里插入图片描述

注意:
  • 安卓下部分机型需要有位置权限才能搜索到设备,需留意是否开启了位置权限
说明:

为了保证准确性建议通过mac地址匹配。

  • advertisData获取mac地址匹配。
  • 通过设备name进行匹配设备,如FIC_992f3e。根据实际情况操作
  • 以下demo提供两种匹配设备方式. 通过macname匹配设备【每个供应商返回的name格式不一样 】
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
/**
* 监听寻找新设备事件
*/
function onBluetoothDeviceFound() {
wx.onBluetoothDeviceFound((res) => {
console.log('广播数据结果:', res);

res.devices.forEach(device => {
if (!device.name && !device.localName) {
return
}

// 转换后, 得出相关数据
var hexStr = ab2hex(device.advertisData);
console.log("广播数据中转换后:advertisData---->" + hexStr);

//通过获取mac匹配
if ((macAddress != "") && (macAddress == device.deviceId) && isnotExist) {
isnotExist = false;
deviceId = device.deviceId;
console.log('android-->tempDeviceId:' + deviceId);

//停止搜寻附近的蓝牙外围设备
stopBluetoothDevicesDiscovery();

//连接设备
createBLEConnection();
}


//通过name匹配设备
let deviceName = device.name.toUpperCase();
if ((deviceName.indexOf(filterDeviceName) != -1) && isnotExist) {
isnotExist = false;
deviceId = device.deviceId;
console.log('ios or android-->tempDeviceId:' + deviceId);

//停止搜寻附近的蓝牙外围设备。
stopBluetoothDevicesDiscovery();

//连接设备
createBLEConnection();
}
})
})
}

3. 开始搜寻附近的蓝牙设备

1
2
wx.startBluetoothDevicesDiscovery(Object object)
注意:`此操作`比较耗费系统资源`,请在`搜索并连接到设备`后调用 `wx.stopBluetoothDevicesDiscovery`方法`停止搜索
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function startBluetoothDevicesDiscovery() {
console.log("执行连接蓝牙设备 回调空===" + _discoveryStarted);
if (_discoveryStarted) {
return;
}
_discoveryStarted = true

wx.startBluetoothDevicesDiscovery({
services: serviceUUID, //如果设置此参数,则只搜索广播包有对应 uuid 的主服务的蓝牙设备。
allowDuplicatesKey: false,
success: (res) => {
console.log('启动搜索蓝牙设备, 结果 :', res)
//onBluetoothDeviceFound() //先调用此方法再使startBluetoothDevicesDiscovery
},
fail(res) {
asddErrorCallback(res.errCode, "");
console.log('startBluetoothDevicesDiscovery fail', res);
}
})
}

4. 停止搜寻附近的蓝牙设备

wx.stopBluetoothDevicesDiscovery(Object object)

1
2
3
4
//停止搜寻附近的蓝牙外围设备。
function stopBluetoothDevicesDiscovery() {
wx.stopBluetoothDevicesDiscovery()
}

5. 连接低功耗蓝牙设备

wx.createBLEConnection(Object object)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
* 连接蓝牙设备
*/
function createBLEConnection() {
var that = this;
wx.createBLEConnection({
deviceId: deviceId,
success: (res) => {
wx.showToast({
title: '设备连接成功',
duration: 2000
})
getBLEDeviceServices(deviceId)
},
fail: (res) => {
console.log('createBLEConnection fail', res);
asddErrorCallback(res.errCode, "");
}
})
//停止搜索
stopBluetoothDevicesDiscovery();
}

6. 获取蓝牙所有服务

1
2
wx.onBLEConnectionStateChange(function callback)
wx.getBLEDeviceServices(Object object)

注意:有些供应商会返回多个服务,只要找自己需要的服务就好

  • 监听蓝牙连接状态,可以处理连接连接意外断开等情况
  • 获取需要的蓝牙服务后,再调用获取蓝牙特征值方法
设备返回数据效果图

假设: 需要的服务是包含EE0的,只需要过滤下就可以【这里通过indexOf

在这里插入图片描述

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
function getBLEDeviceServices(deviceId) {
//监听低功耗蓝牙连接状态的改变事件
wx.onBLEConnectionStateChange(function(res) {
console.log("onBLEConnectionStateChange:", res);
// 该方法回调中可以用于处理连接意外断开等异常情况
console.log(`device ${res.deviceId} state has changed, connected: ${res.connected}`)
if (res.connected == false) {
console.log("连接意外断开等****", _deviceId);
_deviceId = '';
if (flagFromTypes == 1 && flagFromTypes == 2) {
asddErrorCallback(1010, "");
}
}
});

//获取蓝牙所有service
wx.getBLEDeviceServices({
deviceId: deviceId,
success: (res) => {
// console.log("获取蓝牙设备所有服务(service)", res);
for (let i = 0; i < res.services.length; i++) {
let tmpUuid = res.services[i].uuid;
if ((res.services[i].isPrimary) && (tmpUuid.indexOf(filterServiceUUID) != -1)) {
//获取蓝牙特征值
getBLEDeviceCharacteristics(deviceId, res.services[i].uuid)
return
}
}
},
fail: (res) => {
console.log('getBLEDeviceServices fail', res);
asddErrorCallback(res.errCode, "");
}
})
}

7. 获取某个服务中所有特征值

wx.getBLEDeviceCharacteristics(Object object) 获取蓝牙设备某个服务中所有特征值

wx.onBLECharacteristicValueChange(function callback) 监听低功耗蓝牙设备的特征值变化事件

注意: 返回数据中的**properties 的结构**, 里面返回该服务下的特征值是否支持readwritenotifyindicate操作。

返回数据结构效果图

在这里插入图片描述

注意:
  • 根据文档协议读、写、通知服务【前面定义的变量】,遍历对象来获取想要的特征值uuid处理相关逻辑
  • 启用低功耗蓝牙设备特征值变化时的notify功能,注意:必须设备的特征值支持notify 或者indicate 才可以成功调用。
  • 监听低功耗蓝牙设备的特征值变化事件。注意:必须先启用notifyBLECharacteristicValueChange 接口才能接收到设备推送的 notification。
  • 监听onBLECharacteristicValueChange,第一时间获取设备返回的数据
  • 如果需要找到设备后,先获取一些设备信息操作,可以在write中发送相关指令
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
/**
* 获取蓝牙特征值
*/
function getBLEDeviceCharacteristics(deviceId, serviceId) {
console.log("设备:" + deviceId + '******************服务:' + serviceId);
wx.getBLEDeviceCharacteristics({
deviceId: deviceId,
serviceId: serviceId,
success: (res) => {
// console.log('蓝牙设备特征值信息:', res);
console.log('getBLEDeviceCharacteristics success', res.characteristics)
for (let i = 0; i < res.characteristics.length; i++) {
let item = res.characteristics[i]
var itemUUID = item.uuid.toUpperCase(); //转大写

//read操作
if (item.properties.read && itemUUID == writeUUID) {
wx.readBLECharacteristicValue({
deviceId: deviceId,
serviceId: serviceId,
characteristicId: item.uuid,
})
}

//write操作
if (item.properties.write && itemUUID == writeUUID) {
console.log("写 特征值 -----------------------" + item.uuid);
_deviceId = deviceId
_serviceId = serviceId
_characteristicId = item.uuid

//发送 信息查询指令 【根据需求】
if (flagFromTypes == 1 || flagFromTypes == 2) { //血压、秤
handleTimeToHex();
}
}


//notify操作,注意调用监听特征值变化
if (notifyUUID == itemUUID) {
if (item.properties.notify || item.properties.indicate) {
console.log('调用notifyBLECharacteristicValueChange前', item.uuid);
wx.notifyBLECharacteristicValueChange({
deviceId: deviceId,
serviceId: serviceId,
characteristicId: item.uuid,
state: true,
success(res) {
console.log('notification通知数据', res);
status = true;
// wx.hideLoading();
},
fail(res) {
console.log('notifyBLECharacteristicValueChange fali', res);
}
})
}
}
}
},
fail: (res) => {
console.log('getBLEDeviceCharacteristics fail', res)
asddErrorCallback(res.errCode, "");
}
})


// 操作之前先监听,保证第一时间获取数据
wx.onBLECharacteristicValueChange(function(res) {
console.log(`characteristic ${res.characteristicId} has changed, now is ${res.value}`)

console.log("操作类型:" + action_type);

var resData = ab2hex(res.value); //转16进制
console.log("设备返回数据--->", resData); //5d0000000001be304d

// 判断不同类型处理数据
if (flagFromTypes == 1) {
console.log('开始调用 血压计=====》处理返回的数据');
bloodPressureObj.filterStr(resData);
}

})
}

8. 写入数据

1
wx.writeBLECharacteristicValue(Object object)
  • 接收hex参数【下发指令数据】,向设备写入二进制数据。注意:注意:必须设备的特征值支持 write 才可以成功调用。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
/**
* 写入数据
*/
function writeData(hex, action = '') {
if (!status) {
return;
}

if (!_deviceId) {
asddWriteErrors('w');
return;
}

setTimeout(() => {
//类型转换
var enDataBuf = new Uint8Array(hex);
var buffer1 = enDataBuf.buffer
console.log("发送内容长度:", buffer1.byteLength)
console.log('写入的数据:' + _deviceId + '服务serviceId---》' + _serviceId + '特征characteristicId---》' + _characteristicId);

wx.writeBLECharacteristicValue({
deviceId: _deviceId,
serviceId: _serviceId,
characteristicId: _characteristicId,
value: buffer1,
success: (res) => {
wx.hideLoading();
console.log("写数据返回结果", res.errMsg);

//项目需求: 发送某个指令后的需要处理回调
if (action == 'lastZero') {
console.log('最后一次写入00需执行回调========》');
//回调 目的: 执行调用提交接口
eyeCareObj.eyeCareCallback();
}
},
fail(res) {
console.log("写数据失败..", res);
asddErrorCallback(res.errCode, "");
}
})
}, 1000)
}

9. 断开蓝牙设备的连接

注意: 断开蓝牙设备连接同时还要关闭蓝牙模块, 否则安卓设备下再次无法搜索到设备

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
/**
* 断开蓝牙连接
*/
function closeBLEConnection() {
//停止搜索
stopBluetoothDevicesDiscovery();
console.log("断开与低功耗蓝牙设备的连接。", deviceId);

if (deviceId) {
wx.closeBLEConnection({
deviceId: deviceId,
success: function(res) {
console.log("closeBLEConnection。success", res);

},
fail: function(res) {
console.log("closeBLEConnection。fail", res);
},
complete: function() {
status = false;
}
})

//关闭蓝牙模块
wx.closeBluetoothAdapter({
success: function(res) {
console.log("closeBluetoothAdapter ==>res:", res);
},
fail: function(error) {
console.log("closeBluetoothAdapter ==>error:", error);
}
})
}

_discoveryStarted = false;
isnotExist = true;
_deviceId = '';
deviceId = '';
}

10. 错误码判断

10006错误码我单独处理, 自动重连操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
function bluetoothStatus(errorType) {
switch (errorType) {
case 10001:
wx.showModal({
title: '提示',
content: '请检查手机蓝牙是否打开',
showCancel: false
})
break;
case 10002:
wx.showToast({
title: '没有找到指定设备',
icon: 'none'
})
break;
case 10003:
wx.showToast({
title: '连接失败',
icon: 'none'
})
closeBLEConnection();
break;
case 10004:
wx.showToast({
title: '没有找到指定服务',
icon: 'none'
})
closeBLEConnection();
break;
case 10005:
wx.showToast({
title: '没有找到指定特征值',
icon: 'none'
})
closeBLEConnection();
break;
case 10007:
case 10008:
case 10013:
wx.showToast({
title: '设备启动失败,请重试',
icon: 'none'
})
break;
case 10009:
wx.showModal({
title: '提示',
content: '当前系统版本过低,请更新版本体验',
showCancel: false
})
break;
case 10012:
wx.showToast({
title: '连接超时',
icon: 'none'
})
break;
}
}

处理数据方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
/**
* 匹配规则: 取名称后面的mac地址
* mac地址: 假设C7:E2:90:17:1A:40
* len: 截取长度为
*/
function getNameMac(macAddress, len, name) {
let clearColonMac = clearSymbol(macAddress);
let lastFourMac = clearColonMac.substring(clearColonMac.length - len);
let strName = name.toUpperCase();
strName = strName + lastFourMac.toUpperCase(); //转大写
console.log('拼接后的' + strName); //abc_171A40
return strName
}

/**
* 去掉 冒号
*/
function clearSymbol(str) {
str = str.replace(/:/g, ""); //取消字符串中出现的所有冒号
return str;
}

/**
* rrayBuffer转16进度字符串
*/
function ab2hex(buffer) {
var hexArr = Array.prototype.map.call(
new Uint8Array(buffer),
function(bit) {
return ('00' + bit.toString(16)).slice(-2)
}
)
return hexArr.join('');
}

关于下篇内容

在实际中使用的案例。

————————————————
版权声明:本文为CSDN博主「Smile_ping」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/Smile_ping/article/details/102938322