1. 程式人生 > >微信小程式分包傳送資料,給微信硬體完成升級

微信小程式分包傳送資料,給微信硬體完成升級

微信小程式分包傳送資料,給微信硬體完成韌體升級

微信硬體升級流程:

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,具體原因不詳。