【NUCLEO-WBA52CG STM32无线产品家族新系列】+6、设计STM32WBA52CG与小程序通信demo
[复制链接]
由于以前接触过微信小程序开发,其实小程序和蓝牙通信并不复杂,在开发之前先介绍下微信小程序项目的结构如下,小程序包含一个描述整体程序的
和多个描述各自页面的
一个小程序主体部分由三个文件组成,app.js小程序逻辑,app.json小程序公共配置,app.wxss小程序公共样式表,必须放在项目的根目录。一个小程序页面由四个文件组成,分别是页面逻辑xx.js,页面结构xx.wxml,页面配置xx.json,页面样式表xx.wxss,描述页面的四个文件必须具有相同的路径与文件名(不一定跟文件夹同名)。
点击搜索蓝牙设备按钮后,就开始搜索周围正在广播的蓝牙设备,并出现在蓝牙设备列表中,可以选中一个蓝牙设备进行连接。
选择蓝牙设备后,然后点击连接蓝牙设备按钮,就会去连接蓝牙设备了,一旦连接成功就会获取蓝牙设备的服务列表和特征值列表,若发现有通知权限的特征值,则会注册通知回调函数,这样一来就会收到功能节点采集的传感器数据了。设置页面的js逻辑代码如下:
// pages/bluetoothconfig/bluetoothconfig.js
const util = require('../../utils/util.js')
var event = require('../../utils/event.js')
var delayTimer; //用来控制是否持续服务发现
var isFound = false;
var app = getApp()
Page({
/**
* 页面的初始数据
*/
data: {
logs: [],
deviceArray: [],
currDeviceID: '请选择...',
currDeviceName:'',
},
//屏幕打开时执行的函数
onLoad: function () {
let that = this;
//接收别的页面传过来的数据
event.on('EnvMonitorSendData2Device', this, function(data) {
//另外一个页面传过来的data是16进制字符串形式
console.log("要发送给蓝牙设备的数据:"+data);
var buffer=that.stringToBytes(data);
var dataView = new Uint8Array(buffer)
dataView = data;
wx.writeBLECharacteristicValue({
deviceId: app.globalData._deviceId,//蓝牙设备 id
serviceId: app.globalData._serviceId,//蓝牙特征值对应服务的 uuid
characteristicId: app.globalData._writeCharacteristicId,//蓝牙特征值的 uuid
value: buffer,//ArrayBuffer 蓝牙设备特征值对应的二进制值
success: function (res) {//接口调用成功的回调函数
console.log('发送成功')
},
fail: function(res) {//接口调用失败的回调函数
//发送蓝牙数据失败
console.log('发送失败')
}
}
)
})
},
onUnload: function() {
event.remove('EnvMonitorSendData2Device', this);
},
bindPickerChange: function(ret){
var array = this.data.deviceArray;
console.log(array[ret.detail.value]);
this.setData({
currDeviceID: array[ret.detail.value]
})
this.printLog("选中:" + array[ret.detail.value]);
},
bleSearchEvent: function(ret){
this.initBLE();
},
bleConfigEvent: function (ret) {
var deviceID = this.data.currDeviceID;
console.log("选中:" + deviceID);
if (util.isEmpty(deviceID) || deviceID == "请选择..."){
util.toastError("请先搜索设备");
return ;
}
var device = deviceID.split('[');
if(device.length <= 1){
util.toastError("请先搜索设备");
return ;
}
var l=deviceID.split('[');
this.printLog("选中蓝牙设备名字:" + l[0]);
this.setData({
currDeviceName: l[0]
})
var id = device[device.length - 1].replace("]", "");
console.log(id);
util.toastError("连接" + id);
this.createBLE(id);
},
bleCloseEvent: function(ret){
var deviceId = this.data.currDeviceID;
wx.showModal({
title: '确定断开设备吗?',
content: deviceId,
showCancel: true
})
var device = deviceId.split('[');
var id = device[device.length - 1].replace("]", "");
this.printLog("断开设备:[" + id+"]...");
this.closeBLE(id,null);
},
initBLE: function() {
this.printLog("启动蓝牙适配器, 蓝牙初始化")
this.setData("启动蓝牙适配器, 蓝牙初始化")
var that = this;
wx.openBluetoothAdapter({
success: function(res) {
console.log(res);
that.findBLE();
},
fail: function(res) {
util.toastError('请先打开蓝牙');
}
})
},
findBLE: function() {
this.printLog("打开蓝牙成功.")
var that = this
wx.startBluetoothDevicesDiscovery({
allowDuplicatesKey: false,
interval: 0,
success: function(res) {
wx.showLoading({
title: '正在搜索设备',
})
console.log(res);
delayTimer = setInterval(function(){
that.discoveryBLE() //3.0 //这里的discovery需要多次调用
}, 1000);
setTimeout(function () {
if (isFound) {
return;
} else {
wx.hideLoading();
console.log("搜索设备超时");
wx.stopBluetoothDevicesDiscovery({
success: function (res) {
console.log('连接蓝牙成功之后关闭蓝牙搜索');
}
})
clearInterval(delayTimer)
wx.showModal({
title: '搜索设备超时',
content: '请检查蓝牙设备是否正常工作,Android手机请打开GPS定位.',
showCancel: false
})
util.toastError("搜索设备超时,请打开GPS定位,再搜索")
return
}
}, 15000);
},
fail: function(res) {
that.printLog("蓝牙设备服务发现失败: " + res.errMsg);
}
})
},
discoveryBLE: function() {
var that = this
wx.getBluetoothDevices({
success: function(res) {
var list = res.devices;
console.log(list);
if(list.length <= 0){
return ;
}
var devices = [];
for (var i = 0; i < list.length; i++) {
//that.data.inputValue:表示的是需要连接的蓝牙设备ID,
//简单点来说就是我想要连接这个蓝牙设备,
//所以我去遍历我搜索到的蓝牙设备中是否有这个ID
/*var name = list[i].name || list[i].localName;
if(util.isEmpty(name)){
continue;
}
if(name.indexOf('MI') >= 0 && list[i].RSSI != 0){
console.log(list[i]);
devices.push(list[i]);
}*/
var deviceId = list[i].deviceId;
if(util.isEmpty(deviceId)){
continue;
}
if(list[i].RSSI != 0){
console.log(list[i]);
devices.push(list[i]);
}
}
console.log('总共有' + devices.length + "个设备需要设置")
if (devices.length <= 0) {
return;
}
that.connectBLE(devices);
},
fail: function() {
util.toastError('搜索蓝牙设备失败');
}
})
},
connectBLE: function(devices){
this.printLog('总共有' + devices.length + "个设备需要设置")
var that = this;
wx.hideLoading();
isFound = true;
clearInterval(delayTimer);
wx.stopBluetoothDevicesDiscovery({
success: function (res) {
that.printLog('搜索蓝牙设备成功之后关闭蓝牙搜索');
}
})
//两个的时候需要选择
var list = [];
for (var i = 0; i < devices.length; i++) {
var name = devices[i].name || devices[i].localName;
list.push(name + "[" + devices[i].deviceId + "]")
}
this.setData({
deviceArray: list
})
//默认选择
this.setData({
currDeviceID: list[0]
})
},
createBLE: function(deviceId){
var app = getApp()
this.printLog("连接: [" + deviceId+"]...");
var that = this;
//连接之前,先断开一下,防止设备已连接了
this.closeBLE(deviceId, function(res){
console.log("预先关闭,再打开");
setTimeout(function(){
wx.createBLEConnection({
deviceId: deviceId,
success: function (res) {
that.printLog("设备连接成功!");
app.globalData.deviceMac=deviceId;
that.getBLEServiceId(deviceId);
},
fail: function (res) {
that.printLog("设备连接失败:" + res.errMsg);
}
})
}, 2000)
});
},
//获取服务UUID
getBLEServiceId: function(deviceId){
this.printLog("获取设备[" + deviceId + "]服务列表")
var that = this;
wx.getBLEDeviceServices({
deviceId: deviceId,
success: function(res) {
console.log(res);
var services = res.services;
if (services.length <= 0){
that.printLog("未找到主服务列表")
return;
}
that.printLog('找到设备服务列表个数: ' + services.length);
for(var i=0;i<services.length;i++)
{
that.printLog("服务UUIDS:["+services[i].uuid+"]");
}
if (services.length == 1)
{
var service = services[0];
that.printLog("服务UUID:["+service.uuid+"] Primary:" + service.isPrimary);
that.getBLECharactedId(deviceId, service.uuid);
}
else//多个主服务
{
for(var i=0;i<services.length;i++)
{
if(services[i].uuid=="51311102-030E-485F-B122-F8F381AA84ED")
{
var service = services[i];
that.printLog("服务UUID:["+service.uuid+"] Primary:" + service.isPrimary);
app.globalData._deviceId=deviceId;
app.globalData._serviceId=service.uuid;
that.getBLECharactedId(deviceId, service.uuid);
}
}
}
},
fail: function(res){
that.printLog("获取设备服务列表失败" + res.errMsg);
}
})
},
getBLECharactedId: function(deviceId, serviceId){
this.printLog("获取设备特征值")
var that = this;
wx.getBLEDeviceCharacteristics({
deviceId: deviceId,
serviceId: serviceId,
success: function(res) {
console.log(res);
//这里会获取到两个特征值,一个用来写,一个用来读
var chars = res.characteristics;
if(chars.length <= 0){
that.printLog("未找到设备特征值")
return ;
}
that.printLog("找到"+serviceId+"特征值个数:" + chars.length);
if(chars.length >=1){
for(var i=0; i<chars.length; i++){
var char = chars[i];
that.printLog("特征值[" + char.uuid + "]")
var prop = char.properties;
if(prop.notify == true)
{
that.printLog("该特征值属性: Notify");
that.recvBLECharacterNotice(deviceId, serviceId, char.uuid);
}
else if(prop.write == true)
{
that.printLog("该特征值属性: Write");
app.globalData._writeCharacteristicId=char.uuid;
that.sendBLECharacterNotice(deviceId, serviceId, char.uuid);
}
else if(prop.read == true)
{
that.printLog("该特征值属性: Read");
that.sendBLECharacterNotice(deviceId, serviceId, char.uuid);
}
else if(prop.indicate == true)
{
that.printLog("该特征值属性: Indicate");
that.sendBLECharacterNotice(deviceId, serviceId, char.uuid);
}
else
{
that.printLog("该特征值属性: 其他");
}
}
}else{
//TODO
}
},
fail: function(res){
that.printLog("获取设备特征值失败")
}
})
},
recvBLECharacterNotice: function(deviceId, serviceId, charId){
//接收设置是否成功
this.printLog("注册Notice 回调函数");
var that = this;
//设备一旦发送数据在此通道,就会立刻收到通知
wx.notifyBLECharacteristicValueChange({
deviceId: deviceId,
serviceId: serviceId,
characteristicId: charId,
state: true, //启用Notify功能
success: function(res) {
wx.onBLECharacteristicValueChange(function(res){
console.log(res);
that.printLog("收到Notify数据: " + that.ab2hex(res.value));
var tmp =that.ab2hex(res.value);
var cdn = that.data.currDeviceName;
that.printLog("当前蓝牙设备名字: " + cdn);
//发送数据到其它页面
if(cdn=='STM32WB55_GattServer')
{
event.emit('environmetDataChanged',tmp);
}
});
},
fail: function(res){
console.log(res);
that.printLog("特征值Notice 接收数据失败: " + res.errMsg);
}
})
},
sendBLECharacterNotice: function (deviceId, serviceId, charId){
var that = this;
var buffer = this.string2buffer(JSON.stringify(cell));
setTimeout(function(){
wx.writeBLECharacteristicValue({
deviceId: deviceId,
serviceId: serviceId,
characteristicId: charId,
value: buffer,
})
}, 1000);
},
closeBLE: function(deviceId, callback){
var that = this;
wx.closeBLEConnection({
deviceId: deviceId,
success: function(res) {
that.printLog("断开设备[" + deviceId + "]成功.");
console.log(res)
},
fail: function(res){
//that.printLog("断开设备[" + deviceId + "]失败!");
},
complete: callback//接口调用结束的回调函数(调用成功、失败都会执行)
})
},
printLog: function(msg){
var logs = this.data.logs;
logs.push(msg);
this.setData({ logs: logs })
},
/**
* 将字符串转换成ArrayBufer
*/
string2buffer(str) {
if (!str) return;
var val = "";
for (var i = 0; i < str.length; i++) {
val += str.charCodeAt(i).toString(16);
}
console.log(val);
str = val;
val = "";
let length = str.length;
let index = 0;
let array = []
while (index < length) {
array.push(str.substring(index, index + 2));
index = index + 2;
}
val = array.join(",");
// 将16进制转化为ArrayBuffer
return new Uint8Array(val.match(/[\da-f]{2}/gi).map(function (h) {
return parseInt(h, 16)
})).buffer
},
/**
* 将ArrayBuffer转换成字符串
*/
ab2hex(buffer) {
var hexArr = Array.prototype.map.call(
new Uint8Array(buffer),
function (bit) {
return ('00' + bit.toString(16)).slice(-2)
}
)
return hexArr.join('');
},
stringToBytes(str) {
var array = new Uint8Array(str.length);
for (var i = 0, l = str.length; i < l; i++) {
array[i] = str.charCodeAt(i);
}
console.log(array);
return array.buffer;
},
myStringToHex(str){
var a='';
if(str.length == 1){
a += "0" + str;
}
else{
a=str;
}
return a.toUpperCase();// 统一大写格式输出
},
inputSSID: function(res) {
var ssid = res.detail.value;
this.setData({
ssid: ssid
})
},
inputPASS: function(res) {
var pass = res.detail.value;
this.setData({
pass: pass
})
}
})
设置页面的逻辑代码除了控制蓝牙连接断开外,还会接收其它页面发来的数据(发给蓝牙设备),一开始在如何实现小程序不同页面之间的通信的时候踩了许多坑,后来在github上找到了一个很好用的开源库,用类似mqtt发布订阅的方式,一个页面订阅某个topic然后注册回调函数,另一个页面直接往这个topic发数据就行了。
点击设备图标就可以跳转到对应的功能界面,如下图是测试界面:提供了电量、二氧化碳浓度、TVOC浓度3个数据的显示。还提供了一个按钮用于控制板载LED,可以扩展到控制继电器从而控制风机等高电压设备实现多种联动功能。
启动后注册设置页面蓝牙数据通知的回调,接收到通知数据后就解析数据,主要是注意数据格式的问题,蓝牙设置页面传过来的数据是16进制字符串形式。发送数据给蓝牙设备则是调用不同页面通信机制 event.emit('EnvMonitorSendData2Device','fefe');实现。与设备的通信是通过订阅通知来实现的,接收到数据后按照自定义格式解析即可:
//屏幕打开时执行的函数
onLoad: function () {
//接收别的页面传过来的数据
event.on('environmetDataChanged', this, function(data) {
//另外一个页面传过来的data是16进制字符串形式
console.log("接收到蓝牙设备发来的数据:"+data)
//bat 1byte
var aa=parseInt(data[1]+data[3],16);
//co2 4byte
var f=parseInt(data[5]+data[7],16);
var g=parseInt(data[9]+data[11],16);
var bb=f*256+g;
//tvoc 4byte
var i=parseInt(data[13]+data[15],16);
var j=parseInt(data[17]+data[19],16);
var cc=i*256+j;
//实时修改显示值
var up0 = "charts[" + 0 + "].data";
var up1 = "charts[" + 1 + "].data";
var up2 = "charts[" + 2 + "].data";
this.setData({
[up0]:aa,
[up1]:bb,
[up2]:cc,
});
})
},
在前面的帖子中已经测试了STM32WBA52CG的广播和通知发送数据功能,因此在设备端只需要按照自定义格式将传感器数据打包发送出去即可。
|