微信小程序分包發送數據,給微信硬件完成升級

微信小程序分包發送數據,給微信硬件完成固件升級

微信硬件升級流程:

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

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