程序功能: 微信小程序连接指定的蓝牙设备,给蓝牙设备发送数据,监听到蓝牙设备回复(需要硬件蓝牙模块处理)的数据后做相应的处理。
实现: 结合硬件设备给蓝牙模块发数据后 能实现控制电机开门动作

小程序和低功耗蓝牙通信数据格式转换的两个函数。

  1. 给蓝牙设备发送数据
/**
 * send 将16进制转ArrayBufer发给设备
 */
function string2buffer(str) {
  let val = ""
  if (!str) return;
  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(",");
  return new Uint8Array(val.match(/[\da-f]{2}/gi).map(function(h) {
    return parseInt(h, 16)
  })).buffer
}
  1. 蓝牙设备回复数据格式,接收后转16进制
/**
 * revice ArrayBuffer转16进制字符
 */
function ab2hext(buffer) {
  if (buffer == "") {
    return "";
  }
  var hexArr = Array.prototype.map.call(
    new Uint8Array(buffer),
    function(bit) {
      return ('00' + bit.toString(16)).slice(-2)
    }
  )
  return hexArr.join('');
}

小程序搜索、连接设备、获取设备数据及开启监听(监听蓝牙回复的数据),该函数写在小程序的新建JS文件中,在index或其他页面可以直接调用函数,使得小程序页面代码简洁。连接过程及逻辑见代码注释。

/**
 * 1.0 初始化
 */
function connectBLE() {
  wx.openBluetoothAdapter({ // 初始化小程序蓝牙模块 到小程序销毁为止
    success: function(res) {
      startBluetoothDevicesDiscovery(); // 2.0
    },
    fail: function(res) {
      wx.showToast({
        title: '请开启蓝牙',
        icon: 'none',
        duration: 5000 // 显示5秒
      })
    }
  })
}

/** 
 * 2.0 搜索特定设备
 */
function startBluetoothDevicesDiscovery() {
  wx.showLoading({
    title: '正在搜索设备',
  })
  wx.startBluetoothDevicesDiscovery({
    success: function(res) {
      console.log("搜索了外围设备:" + res);
      wx.onBluetoothDeviceFound(function(res) {  // 监听新设备事件
        let devices = res.devices;
        console.log("监听新设备事件:" + devices);
        if (devices != null && devices != "") {
          var mac = app.appData.mac;  // 特定设备mac(实际为设备在广播中name字段的信息,唯一标识),我定在app.js中方便调用。
          wx.setStorageSync('mac', mac)
          for (var i = 0; i < devices.length; i++) {
            if (devices[i].localName == mac || devices[i].name == mac) {
              var deviceId = devices[i].deviceId; // 注意ios搜索的为设备id,不同的ios搜索同一个蓝牙设备的id可能不同。安卓搜索的id为设备mac地址。不可以使用id作为设备唯一标识!
              wx.setStorageSync('deviceId', deviceId)
              console.log("搜索到目标设备 缓存deviceId" + wx.getStorageSync('deviceId'));
              connetBlue(deviceId); // 3.0
            }
          }
        } else {
          console.log("监听新设备为空!");
        }
      })
    },
    fail(res) {
      console.log("搜索外围设备失败!", res);
    }
  })
}

/**
 * 3.0 获取到设备之后连接蓝牙设备
 */
function connetBlue(deviceId) {
  wx.hideLoading(); // 隐藏正在搜索提示
  wx.showLoading({
    title: '正在连接',
  })
  wx.createBLEConnection({ // 使用设备Id连接设备。
    deviceId: wx.getStorageSync('deviceId'),
    success: function(res) {
      console.log("连接蓝牙成功!")
      wx.stopBluetoothDevicesDiscovery({
        success: function(res) {
          console.log('连接蓝牙成功之后关闭蓝牙搜索!');
        }
      })
      // 该方法回调中可以用于处理连接意外断开等异常情况
      wx.onBLEConnectionStateChange(function(res) 
        console.log('设备:', res.deviceId, ' 连接状态改变, connected:', res.connected)
      })
      getServiceId() // 5.0
    },
    fail: function(res) {
      console.log("连接失败,错误代码:" + res.errCode + ':' + res.errMsg);
    }
  })
}

/**
 * 5.0 获取serviceId
 */
function getServiceId() {
  wx.getBLEDeviceServices({
    deviceId: wx.getStorageSync('deviceId'),
    success: function(res) {
      console.log("获取serviceId成功");
      insertDeviceAndbindUser(); // 不绑定服务器的小伙伴可直接调用函数6.0; 调用服务器,绑定设备。将设备唯一标识和小程序用户唯一标识(openId)绑定。openId需要调用接口wx.login API,具体见小程序开发文档:https://developers.weixin.qq.com/miniprogram/dev/api/open-api/login/wx.login.html
    },
    fail: function(res) {
      wx.hideLoading();
      console.log("获取serviceId失败!", res);
    }
  })
}

/**
 * 5.1 将设备mac录入数据库,绑定用户
 */
function insertDeviceAndbindUser() {
  wx.request({
    url: app.appData.url + '/api/xxxx', // app.js定义服务器ip及端口
    method: 'POST',
    data: {
      openId: openId
      mac: app.appData.mac // 调试mac,实际为获取的设备唯一标识
    },
    header: {
      'content-type': 'application/json; charset=UTF-8'
    },
    success: function(res) {
      console.log("设备录入绑定-访问服务器成功!", res);
      if (res.data.code == 206) { // 服务器自定义反码
        wx.showToast({
          title: '设备已被绑定',
          icon: 'none',
          duration: 4000
        })
      } else {
          wx.showToast({
        	title: '绑定成功',
      	  })
        getCharacteId(); // 6.0
      }
    },
    fail: function(res) {
      console.log("用户绑定-服务器返回失败", res);
    }
  })
}

/**
 * 6.0 获取特征值
 */
function getCharacteId() {
  wx.getBLEDeviceCharacteristics({
    deviceId: wx.getStorageSync('deviceId'),
    serviceId: app.appData.serviceId, 
    success: function(res) {
      console.log("获取特征值成功");
      startNotice() //7.0
    },
    fail: function(res) {
      console.log("获取特征值失败!", res);
    }
  })
}

/**
 * 7.0 发送指令与监听数据
 */
function startNotice() {
  //第一步 开启监听 notityid   第二步发送指令 write
  wx.notifyBLECharacteristicValueChange({
    state: true,
    deviceId: wx.getStorageSync('deviceId'),
    serviceId: app.appData.serviceId,
    characteristicId: app.appData.notifyUUID,
    success: function(res) {
      console.log("监听成功");
      sendData('1'); // 发送数据
      wx.hideLoading();
      // 此时可以拿到蓝牙设备返回来的数据是一个ArrayBuffer类型数据,所以需要通过一个方法转换成字符串
      wx.onBLECharacteristicValueChange(function(res) {
        console.log("监听设备回应:", res.value);
        var nonceId = ab2hext(res.value);
        console.log("设备回应:", nonceId); // 开门: ffff000404170120
        // 回应标志位
        var nonceFlag = nonceId.substring(8, 10);
        if (nonceFlag === '04') {
          wx.showToast({
            title: '开箱成功',
          })
        }
        analysisRevice(nonceId); // 9.0

        setTimeout(function() {
          wx.switchTab({
            url: '../index/index'
          });
        }, 500) //延迟时间 这里是0.5秒

      })
    },
    fail: function(res) {
      console.log("监听开启失败:", res);
    }
  })
}

/**
 * 8.0 写入数据,指令代号与数值
 */
function sendData(code) {
  wx.request({
    url: app.appData.url + '/api/xxxxxx',
    method: 'POST',
    data: {
      num: code,
      val: ""
    },
    header: {
      'content-type': 'application/json; charset=UTF-8'
    },
    success: function(res) {
      console.log("服务器访问成功", res);
      var serverResData = res.data;
      console.log("serverResData:", serverResData);
      var buffer = string2buffer(serverResData);

      wx.writeBLECharacteristicValue({
        deviceId: wx.getStorageSync('deviceId'),
        serviceId: app.appData.serviceId,
        characteristicId: app.appData.writeUUID,
        value: buffer, // 这里的value是ArrayBuffer类型
        success: function(res) {
          console.log("写入成功");
        },
        fail: function() {
          console.log("写入失败,从新连接");
          wx.showToast({
            title: '设备已断开,正在重新连接...',
            icon: 'none'
          })
          ReConnetBlue();
          console.log("从新连接...");
 
        }
      })
    },
    fail: function(res) {
      console.log("服务器访问失败", res);
    }
  })
}

/**
 * 9.0 拿到设备返回的值后,去后台请求处理逻辑关系
 */
function analysisRevice(nonceId) {
  wx.request({
    url: app.appData.url + '/api/xxxxx',
    method: 'POST',
    data: {
      mac: app.appData.mac,
      hex: nonceId,
    },
    header: {
      'content-type': 'application/json; charset=UTF-8'
    },
    success: function(res) {
      console.log("解析数据-访问服务器成功", res);
      var resultRes = res.data;
    },
    fail: function(res) {
      console.log("解析数据-服务器返回openId失败", res);
    }
  })
}

连接成功和用户绑定后,如果需要删除设备需要关闭蓝牙适配器,才能重新搜索到当前的设备。

function closeBluetoothAdapter() {
	wx.closeBluetoothAdapter({
		success(res) {
			console.log('关闭成功!', res)
		},
        fail: function() {
        	console.log('关闭失败!', res)
        }
    })
}

第一次写小程序控制低功耗蓝牙设备,欢迎大家一起讨论。