微信小程序分包發送數據,給微信硬件完成固件升級
微信硬件升級流程:
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,具體原因不詳。