微信小程式分包傳送資料,給微信硬體完成升級
微信小程式分包傳送資料,給微信硬體完成韌體升級
微信硬體升級流程:
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,具體原因不詳。