微信小程序分包发送数据,给微信硬件完成升级

微信小程序分包发送数据,给微信硬件完成固件升级

微信硬件升级流程:

1.准备升级固件包,我们使用的是zip包,实际使用的时候可以放在服务器下载。

2.扫描ble设备并连接,向设备写入10,进入dfu模式。

3.进入dfu之后蓝牙会断开,需要重新链接,另外,duf模式下,蓝牙的deviceid会改变(Android手机上搜到的是macaddress),设备名称也会改变,这个根据你们喜好和硬件小伙伴自行沟通设置,我们设置的是DfuTarg。

4.ble蓝牙对应有多个服务(service),每个服务对应有多个特征值(Characteristic),升级的时候主要用到1530这个服务,和1531,1532这两个对应的特征值。这些在进入dfu模式下都能搜索到。

5.连接设备,解压对应的zip包。开启notify,开启notify,开启notify(重要的事情说三遍,不然后面接受不到固件发来的信息),解压完之后对应三个文件bin,dat和.json。

6.向特征值1531写入0104(代表进入升级模式),向1532写入bin文件的包长(12字节),在微信的onBLECharacteristicValueChange会收到来自固件传来的指令100101,向1531写入0200,向1532写入dat文件的长度(14字节),收到100201的notify值,向1531写入03,向1532写入bin包的类容,注意分包发送,ble蓝牙传输数据每次限制在20字节,超过会报错(分包下面再说)。写完后收到100301,向1531写入04,收到100401指令,向1531写入05,之后固件会重启,ble的mac地址和deviceid会恢复,重新链接,写入对应的配置信息,如自检,授权等。

固件升级详细流程参照

小程序升级代码 api

1,下载zip包,微信访问链接只支持https协议,另外还需配置域名才能访问。配置流程

//下载升级包
const fm = wx.getFileSystemManager()
const rootPath=wx.env.USER_DATA_PATH
function downloadZip(){
  	return new Promise((resolve,reject)=>{
      let path=rootPath+"/"+fireWare
      createDirPath(path).then(function(res){
        update()
      }).catch(function(err){
        console.log(err)
      })
  	})
}

function update(){
  wx.downloadFile({
    url:"zip包对应的链接",
    success:function(res){
      tempFilePath:res.tempFilePath
      //这里有个坑,真机调试时,我在ios上下载zip包,和服务器上一模一样
      //但在Android上操作时,会多出7个字节,只能强制删除这七个字节
      //Android只有真机调试会出现,预览和体验版的时候不会,暂时不知道原因
      fm.writeFile({
        filePath:rootPath+"/"+fireWare+"/fireWare.zip",
        data:res.data,
        success:function(res){
          
        },
        fail:function(err){
          
        }
      })
    },
    fail:function(err){
      
    }
  })
}

function createDirPath(dirpath){
  return new Promise((resolve, reject) => {
      fm.access({
          path: dirPath,
          success: function(res) {
             resolve(res)
           },
          fail: function(res) {
              fm.mkdir({
              dirPath: dirPath,
              recursive: true,
              success: function(res) {
                  resolve(res)
              },
              fail: function(res) {
                  console.log(res)
                 reject(res)
              }
         })
         }
      })
 })}


蓝牙相关工具类bleUtils:
const openBluetootnAdapter=obj=>{
   return new Promise((resolve,reject)={
     obj.success=function(res){
       resolve(res)
     },
     obj.fail=function(err){
       reject(err)
     }
     wx.openBluetoothAdapter(obj)
   })
}

const closeBluetoothAdapter = obj => {
  return new Promise((resolve, reject) => {
    obj.success = function (res) {
      resolve(res)
    }
    obj.fail = function (res) {
        reject(res)
    }
    wx.closeBluetoothAdapter(obj)
  })
}

const getBluetoothAdapterState = obj => {
  return new Promise((resolve, reject) => {
    obj.success = function (res) {
      resolve(res)
    }
    obj.fail = function (res) {
      reject(res)
    }
    wx.getBluetoothAdapterState(obj)
  })
}

const startBluetoothDevicesDiscovery = obj => {
  return new Promise((resolve, reject) => {
    obj.success = function (res) {
      resolve(res)
    }
    obj.fail = function (res) {
      reject(res)
    }
    wx.startBluetoothDevicesDiscovery(obj)
  })
}
const stopBluetoothDevicesDiscovery = obj => {
  return new Promise((resolve, reject) => {
    obj.success = function (res) {
      resolve(res)
    }
    obj.fail = function (res) {
      reject(res)
    }
    wx.stopBluetoothDevicesDiscovery(obj)
  })
}

const getBluetoothDevices = obj => {
  return new Promise((resolve, reject) => {
    obj.success = function (res) {
      resolve(res)
    }
    obj.fail = function (res) {
      reject(res)
    }
    wx.getBluetoothDevices(obj)
  })
}

const getConnectedBluetoothDevices = obj => {
  return new Promise((resolve, reject) => {
    obj.success = function (res) {
      resolve(res)
    }
    obj.fail = function (res) {
      reject(res)
    }
    wx.getConnectedBluetoothDevices(obj)
  })
}

const createBLEConnection = obj => {
  return new Promise((resolve, reject) => {
    obj.success = function (res) {
      resolve(res)
    }
    obj.fail = function (res) {
      reject(res)
    }
    wx.createBLEConnection(obj)
  })
}

const closeBLEConnection = obj => {
  return new Promise((resolve, reject) => {
    obj.success = function (res) {
      resolve(res)
    }
    obj.fail = function (res) {
      reject(res)
    }
    wx.closeBLEConnection(obj)
  })
}

const getBLEDeviceServices = obj => {
  return new Promise((resolve, reject) => {
    obj.success = function (res) {
      resolve(res)
    }
    obj.fail = function (res) {
      reject(res)
    }
    wx.getBLEDeviceServices(obj)
  })
}

const getBLEDeviceCharacteristics = obj => {
  return new Promise((resolve, reject) => {
    obj.success = function (res) {
      resolve(res)
    }
    obj.fail = function (res) {
      reject(res)
    }
    wx.getBLEDeviceCharacteristics(obj)
  })
}

const readBLECharacteristicValue = obj => {
  return new Promise((resolve, reject) => {
    obj.success = function (res) {
      resolve(res)
    }
    obj.fail = function (res) {
      reject(res)
    }
    wx.readBLECharacteristicValue(obj)
  })
}

const writeBLECharacteristicValue = obj => {
  return new Promise((resolve, reject) => {
    obj.success = function (res) {
      resolve(res)
    }
    obj.fail = function (res) {
      reject(res)
    }
    wx.writeBLECharacteristicValue(obj)
  })
}

const notifyBLECharacteristicValueChange = obj => {
  return new Promise((resolve, reject) => {
    obj.success = function (res) {
      resolve(res)
    }
    obj.fail = function (res) {
      reject(res)
    }
    wx.notifyBLECharacteristicValueChange(obj)
  })
}

module.exports = {
  openBluetoothAdapter: openBluetoothAdapter,
  closeBluetoothAdapter: closeBluetoothAdapter,
  getBluetoothAdapterState: getBluetoothAdapterState,
  onBluetoothAdapterStateChange: onBluetoothAdapterStateChange,
  startBluetoothDevicesDiscovery: startBluetoothDevicesDiscovery,
  stopBluetoothDevicesDiscovery: stopBluetoothDevicesDiscovery,
  getBluetoothDevices: getBluetoothDevices,
  getConnectedBluetoothDevices: getConnectedBluetoothDevices,
  onBluetoothDeviceFound: onBluetoothDeviceFound,
  createBLEConnection: createBLEConnection,
  closeBLEConnection: closeBLEConnection,
  getBLEDeviceServices: getBLEDeviceServices,
  getBLEDeviceCharacteristics: getBLEDeviceCharacteristics,
  readBLECharacteristicValue: readBLECharacteristicValue,
  writeBLECharacteristicValue: writeBLECharacteristicValue,
  notifyBLECharacteristicValueChange: notifyBLECharacteristicValueChange,
  onBLEConnectionStateChange: onBLEConnectionStateChange,
  onBLECharacteristicValueChange: onBLECharacteristicValueChange
}

2.扫描并连接设备

function scanDevice(){
  bleUtils.openBluetoothAdapter({}).then(function(res){//初始化蓝牙模块儿
    return bleUtil.getBluetoothAdapterState({})//获取适配器状态
  }).then(function(res){
    if(res.available){//蓝牙可用
      bleUtils.startBluetoothDevicesDiscovery({
        services:["Fee7"]//过滤,只搜索微信硬件设备
       	allowDuplicatesKey:true,
       	interval:0.1
      }).then(function(res){
        bleCallback()
      })
    }
  })
}

function bleCallback(){
  bleUtils.onBluetoothAdapterStateChange(function(res){//蓝牙转态回调
    
  }),
  bleUtils.onBLEConnectionStateChange(function(res){//链接状态回调
    
  })
  bleUtils.onBluetoothDeviceFound(function(devices){
    //搜索到的蓝牙设备回调,对应可以将相关信息显示在界面上
  })
}

//点击界面设备列表的时候可以拿到对应的device
//链接设备并写入10指令
function connectDevice(device){
  bleUtils.createBLEConnection({
    deviceId:device.deviceId,
    timeOut:5000
  }).then(function(res){
    //设备链接成功后记得停止扫描
    bleUtils.stopBluetoothDevicesDiscovery({})
    return bleUtils.getBLEDeviceServices({//获取设备对应的服务
      deviceId: device.deviceId
    })
  }).then(function(res){
    device.fee7 = res.services[0]
    return bleUtils.getBLEDeviceCharacteristics({//获取特征值
        deviceId: device.deviceId,
        serviceId: device.fee7["uuid"]
      })
  }).then(function(res){
    for (var i in res.characteristics) {
        var c = res.characteristics[i]
        if (c.uuid == '0000FEC7-0000-1000-8000-00805F9B34FB') {
          device.fec7 = c
        }
      }
      var hex = 'FE01000E753100000A0012020110'//对应的进入dfu的指令
      var buffer = util.bufferFromHex(hex)
      return bleUtil.writeBLECharacteristicValue({
        deviceId: device.deviceId,
        serviceId: device.fee7["uuid"],
        characteristicId: device.fec7.uuid,
        value: buffer
      })
  }).then(function(res){//关闭蓝牙
    bleUtil.closeBLEConnection({
        deviceId: device.deviceId
      })
      bleUtil.closeBluetoothAdapter({})//关闭adapter,否则后面会在部分Android机上搜不到dfu
      //跳转到dfuConfig页面
      wx.navigateTo({
        url:".../.../dfuConfig"
      })
  })
}

//util工具类:
const hexFromBuffer = buffer => {
  var hexArr = Array.prototype.map.call(
    new Uint8Array(buffer),
    function (bit) {
      return ('00' + bit.toString(16)).slice(-2)
    }
  )
  return hexArr.join('');
}

const bufferFromHex = hex => {
  var typedArray = new Uint8Array(hex.match(/[\da-f]{2}/gi).map(function (h) {
    return parseInt(h, 16)
  }))
  return typedArray.buffer
}

3.进入duf界面,也是先扫描,获取设备,显示设备,点击连接设备,开启notify。前面的扫描和现实与上面的一致,这里就不重复了,直接进入连接设备。注意这里扫描时候我们使用的时候过滤条件是services:[‘00001530-1212-EFDE-1523-785FEABCD123’]

function connectDfuDevice(device){
  let self=this
  bleUtils.createBLEConnection({
    deviceId:device.deviceId
    timeOut:5000
  }).then(function(res){
    bleUtils.stopBluetoothDevicesDiscovery({})
    return bleUtil.getBLEDeviceServices({
        deviceId: targetDevice.deviceId
      })
  }).then(function(res){
    device.server1530=res.services[0]
    return bleUtil.getBLEDeviceCharacteristics({//获取服务1530对应的特征值1531和1532
        deviceId: device.deviceId,
        serviceId: device.server1530["uuid"]
      })
  }).then(function(res){
    for (var i in res.characteristics) {
        var c = res.characteristics[i]
        if (c.uuid == self.data.uuid1531) {//00001531-1212-EFDE-1523-785FEABCD123
          device.characteristic1531 = c
        }
        if (c.uuid == self.data.uuid1532) {//00001532-1212-EFDE-1523-785FEABCD123
          device.characteristic1532 = c
        }
      }
      return bleUtil.notifyBLECharacteristicValueChange({//开启1531的notify
        deviceId: device.deviceId,
        serviceId: device.server1530["uuid"],
        characteristicId: device.characteristic1531["uuid"],
        state: true
      })
  }).then(function(res){
    return self.bleWriteTo1531(targetDevice, "0104")//向1531写入0104进入升级模式
  }).then(function(res){
     let arrayBuffer = new ArrayBuffer(12)
     let int32Array = new Uint32Array(arrayBuffer);
     let length = self.data.dfuPackage.binData.byteLength//解压zip包后的bin文件长度
     int32Array[2] = length
     //向1532写入bin包长,固件会给手机发送消息
     return self.bleWriteTo1532(targetDevice, util.hexFromBuffer(arrayBuffer))
  })
}

点击界面的时候解压zip包,分别得到三个文件:.bin .dat和json文件,分别读取三个文件,拿到对应的buffer并返回存起来,上面写入bin文件的长度就是对应解压后的bin的buffer.byteLength 详细解压请查看官方api unzip

//向1531写入数据
function bleWriteTo1531(device,data){
  data = util.bufferFromHex(data)
  return bleUtil.writeBLECharacteristicValue({
      deviceId: device.deviceId,
      serviceId: device.server1530["uuid"],
      characteristicId: device.characteristic1531["uuid"],
      value: data
    })
}
//向1532写入数据
bleWriteTo1532: function(device, data) {
    if (typeof(data) == 'string') {
      data = util.bufferFromHex(data)
    }
    return bleUtil.writeBLECharacteristicValue({
      deviceId: device.deviceId,
      serviceId: device.server1530["uuid"],
      characteristicId: device.characteristic1532["uuid"],
      value: data
    })
  },
  
  //收到固件传来的信息处理,开启notify后,向1531写入信息后,固件会传回信息
  function callback(device){
  let self=this
    bleutils.onBLECharacteristicValueChange(function(res){//固件信息回调
      let hexValue = util.hexFromBuffer(res.value)//将信息转为16进制
      if(hexValue=='100101'){//上面写入0104之后会有这个回调
       	//向1531写入0200
       	self.bleWriteTo1531(device,'0200').then(function(res){
          //向1532写入.dat文件(14字节)
          let hexString = util.hexFromBuffer(self.data.dfuPackage.datData)
          return self.bleWriteTo1532(device, hexString)
       	}).then(function(res){
          //向1531写入0201
          return self.bleWriteTo1531(device, "0201")
       	})
      }else if(hexValue == '100201'){
        //向1531写入03
        self.bleWriteTo1531(device, "03").then(function(res) {
          console.log(res)
         //向1532写入bin文件大小,注意分包写,有两种方法可以分包,下面单独解释
          self.bleWriteBinFile(device, self.data.dfuPackage, 0)
     
        })
      }else if(hexValue == '100301'){
      //向1531写入04
         self.bleWriteTo1531(targetDevice, "04").then(function(res) {
          console.log(res)
        })
      }else if(hexValue == '100401'){
      //写入05
        self.bleWriteTo1531(targetDevice, "05").then(function(res) {
         
        })
      }
    })
  }  
  //升级流程就是这么繁琐,而且每个流程都必须走完才能算升级完成

分包发送数据

  由于蓝牙每次发送只能传输20字节,所以在发送bin文件时候要分包,下面介绍两种方案:
  //方法1:通过循环一直往固件写数据
  function bleWriteBinFile(device, dfuPackage, offset){
     let self = this
    let start = offset
    let length = dfuPackage.binData.byteLength
    for (; offset < length; offset = offset + 20) {
      
      let step = offset + 20 > length ? length - offset : 20
      let uint8Array = new Uint8Array(dfuPackage.binData, offset, step)
      let hex = ""
      for (let i in uint8Array) {
        let num = uint8Array[i];
        if (num < 16) {
          hex += '0'
        }
        hex += num.toString(16)
      }
      console.log(hex)
      let targetData = util.bufferFromHex(hex)
      wx.writeBLECharacteristicValue({
        deviceId: device.deviceId,
        serviceId: device.server1530["uuid"],
        characteristicId: device.characteristic1532["uuid"],
        value: targetData,
        fail: function(err) {
          offset = offset - 20//失败了重写一遍
          console.log('write bin fail', err)
        }
      })
      let percentage = (offset + step) / length
      percentage = (percentage * 100).toFixed(1)
      wx.showLoading({
        title: '写入' + percentage + '%',
        mask: true
      })

      if (offset + step == length) {
        wx.showToast({
          title: '写入完成',
        })
        // self.writeConfigInfo(device)
        break
      }
      var timestamp1 = (new Date()).getTime();
      var timestamp2 = (new Date()).getTime();
      while (timestamp2 - timestamp1 < 40) {
        timestamp2 = (new Date()).getTime();
      }

      if (offset - start == 1000) {
        setTimeout(function(res) {
          self.bleWriteBinFile(device, self.data.dfuPackage, offset + 20)
        }, 100)
        return;
      }
    }
  }
  
  //方法2:用递归写入,成功之后写入下个数据,适合数据量不是很大时调用
  function bleWriteBinFile(device, dfuPackage, offset){
    let self = this
    let start = offset
    let length = dfuPackage.binData.byteLength

    let step = offset + 20 > length ? length - offset : 20
    let uint8Array = new Uint8Array(dfuPackage.binData, offset, step)
    let hex = ""
    for (let i in uint8Array) {
      let num = uint8Array[i];
      if (num < 16) {
        hex += '0'
      }
      hex += num.toString(16)
    }
    console.log(hex)
    let targetData = util.bufferFromHex(hex)

    wx.writeBLECharacteristicValue({
      deviceId: device.deviceId,
      serviceId: device.server1530["uuid"],
      characteristicId: device.characteristic1532["uuid"],
      value: targetData,
      fail: function(err) {//失败了重新写入
        console.log('write bin fail', err)
        self.writeBinFileToAndroid(device, dfuPackage, offset)
        // setTimeout(function(){//部分Android机需要延时写入
         
        // },250)
      },
      success: function() {
        offset = offset + 20//成功了之后写入下一条数据
        if (offset < length) {
          self.writeBinFileToAndroid(device, dfuPackage, offset)
        }
        // setTimeout(function() {部分Android机需要延时写入
         
        // }, 250)
      }
    })

    let percentage = (offset + step) / length
    
    percentage = (percentage * 100).toFixed(1)
    wx.showLoading({
      title: '写入' + percentage + '%',
      mask: true
    })
  }
  
  //至此,ble升级基本完成,后面写入配置信息就不介绍了,和最开始写入信息一样

总结

1.蓝牙断开重连的时候,要调用closeBluetoothAdapter,否则在部分Android机上搜索不到设备。

2.收取固件传回来的信息记得开启notify。

3.在真机调试下,Android机上执行downloadfile接口时,读取res.temFilePath比header的content-length多7个字节,但在预览模式和体验版上ok,具体原因不详。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章