miniprogram: add iap mp for ble OTA
This commit is contained in:
2
tools/Mini_Program/iap/README.md
Normal file
2
tools/Mini_Program/iap/README.md
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
## 蓝牙升级微信小程序源码
|
||||||
|
|
12
tools/Mini_Program/iap/miniprogram/app.js
Normal file
12
tools/Mini_Program/iap/miniprogram/app.js
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
//app.js
|
||||||
|
App({
|
||||||
|
globalData: {
|
||||||
|
},
|
||||||
|
onLaunch: function () {
|
||||||
|
this.globalData.SystemInfo = wx.getSystemInfoSync()
|
||||||
|
//console.log(this.globalData.SystemInfo)
|
||||||
|
wx.setKeepScreenOn({
|
||||||
|
keepScreenOn: true
|
||||||
|
})
|
||||||
|
},
|
||||||
|
})
|
14
tools/Mini_Program/iap/miniprogram/app.json
Normal file
14
tools/Mini_Program/iap/miniprogram/app.json
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"pages": [
|
||||||
|
"pages/hardware/ble/index/index",
|
||||||
|
"pages/hardware/ble/device/index"
|
||||||
|
],
|
||||||
|
"window": {
|
||||||
|
"backgroundTextStyle": "light",
|
||||||
|
"navigationBarBackgroundColor": "#f8f8f8",
|
||||||
|
"navigationBarTitleText": "TencentOS tiny 蓝牙OTA示例",
|
||||||
|
"navigationBarTextStyle": "black",
|
||||||
|
"backgroundColor": "#f8f8f8"
|
||||||
|
},
|
||||||
|
"sitemapLocation": "sitemap.json"
|
||||||
|
}
|
51
tools/Mini_Program/iap/miniprogram/app.wxss
Normal file
51
tools/Mini_Program/iap/miniprogram/app.wxss
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
/**app.wxss**/
|
||||||
|
/* .container {
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 100rpx 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
} */
|
||||||
|
|
||||||
|
|
||||||
|
.flex-row {
|
||||||
|
display: flex;
|
||||||
|
flex-flow: row nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flex-row-wrap {
|
||||||
|
display: flex;
|
||||||
|
flex-flow: row wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flex-column-reverse {
|
||||||
|
display: flex;
|
||||||
|
flex-flow: column-reverse nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.center {
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.left {
|
||||||
|
justify-content: flex-start;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.right {
|
||||||
|
justify-content: flex-end;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.space-between {
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.space-around {
|
||||||
|
justify-content: space-around;
|
||||||
|
align-items: center;
|
||||||
|
}
|
BIN
tools/Mini_Program/iap/miniprogram/images/aithinker-mini.png
Normal file
BIN
tools/Mini_Program/iap/miniprogram/images/aithinker-mini.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 84 KiB |
BIN
tools/Mini_Program/iap/miniprogram/images/bluetooth.png
Normal file
BIN
tools/Mini_Program/iap/miniprogram/images/bluetooth.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 9.4 KiB |
@@ -0,0 +1,441 @@
|
|||||||
|
// 1. 小程序发布版本或者体验版本,连续写ble需要加延时,否则会导致写失败1008。本地调试本身有延时,因此不需要加,需要注释掉
|
||||||
|
// 2. 安卓系统调试蓝牙会有黏包问题。[ACK, ACK, ..] [C, C] ...
|
||||||
|
// 3. ymodem.h DOWNLOAD_TIMEOUT 配置需要注意,过小的话,写block时间过长会导致接收端超时,重新进入普通模式
|
||||||
|
// 4. NAK 重传逻辑暂不支持
|
||||||
|
// https://www.amobbs.com/thread-5704281-1-1.html
|
||||||
|
|
||||||
|
const app = getApp()
|
||||||
|
const util = require('../../../../utils/util.js');
|
||||||
|
const bleapi = require('../../../../utils/hardware/ble/ble-api.js');
|
||||||
|
const Packet = require('../../../../utils/hardware/ble/packet.js')
|
||||||
|
|
||||||
|
var msgQueue = []
|
||||||
|
var fileBuffer
|
||||||
|
var fileName
|
||||||
|
var bUse1K = true
|
||||||
|
var DownLoadMode = 0
|
||||||
|
const SyncTimeout = 15000 // 等待C
|
||||||
|
const RecvTimeout = 5000 // 等待ACK
|
||||||
|
|
||||||
|
/*
|
||||||
|
SOH 0x01 协议头(128bytes类型)
|
||||||
|
STX 0x02 协议头(1k类型)
|
||||||
|
EOT 0x04 传输结束
|
||||||
|
ACK 0x06 接收响应
|
||||||
|
NAK 0x15 失败响应
|
||||||
|
CAN 0x18 取消传输
|
||||||
|
C 0x43 开启文件传输
|
||||||
|
*/
|
||||||
|
const EOT = 0x04
|
||||||
|
const ACK = 0x06
|
||||||
|
const CAN = 0x18
|
||||||
|
const NAK = 0x15
|
||||||
|
const C = 0x43
|
||||||
|
|
||||||
|
Page({
|
||||||
|
data: {
|
||||||
|
device: {
|
||||||
|
// connected: true,
|
||||||
|
// deviceId: "90:9A:77:26:5D:64",
|
||||||
|
// name: "HC-08",
|
||||||
|
},
|
||||||
|
receiveText: '',
|
||||||
|
downloadLog: '',
|
||||||
|
},
|
||||||
|
acceptDataFromPrevPage() {
|
||||||
|
const eventChannel = this.getOpenerEventChannel()
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
eventChannel.on('eventData', function (data) {
|
||||||
|
resolve(data)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
},
|
||||||
|
onLoad: async function(options) {
|
||||||
|
console.log(util.formatTime())
|
||||||
|
let data = await this.acceptDataFromPrevPage()
|
||||||
|
console.log("accept data from prev page", data)
|
||||||
|
this.setData({
|
||||||
|
device: data.device,
|
||||||
|
})
|
||||||
|
|
||||||
|
// 监听低功耗蓝牙连接状态的改变事件。包括开发者主动连接或断开连接,设备丢失,连接异常断开等等
|
||||||
|
await bleapi.onBLEConnectionStateChange(this.onStateChange)
|
||||||
|
// 监听低功耗蓝牙设备的特征值变化事件。必须先启用 notifyBLECharacteristicValueChange 接口才能接收到设备推送的 notification
|
||||||
|
if (this.data.device.characteristic.properties.notify == true) {
|
||||||
|
console.log("notifyBLECharacteristicValueChange and register value change callback")
|
||||||
|
await bleapi.notifyBLECharacteristicValueChanged(
|
||||||
|
this.data.device.deviceId,
|
||||||
|
this.data.device.service.uuid,
|
||||||
|
this.data.device.characteristic.uuid)
|
||||||
|
await bleapi.onBLECharacteristicValueChange(this.onMessage)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onUnload: function(e) {
|
||||||
|
let deviceId = this.data.device.deviceId
|
||||||
|
console.log("onUnload close the ble connection", deviceId)
|
||||||
|
bleapi.closeBLEConnection(deviceId)
|
||||||
|
},
|
||||||
|
onStateChange: function(res) {
|
||||||
|
console.log("onBLEConnectionStateChange", res.connected)
|
||||||
|
this.setData({
|
||||||
|
[`device.connected`]: res.connected
|
||||||
|
})
|
||||||
|
},
|
||||||
|
formSubmit: async function(e) {
|
||||||
|
console.log(e.detail.value)
|
||||||
|
let value = e.detail.value.textarea
|
||||||
|
try {
|
||||||
|
await this.writeData(util.str2abUint8(value))
|
||||||
|
wx.showToast({
|
||||||
|
icon: 'none',
|
||||||
|
title: '写数据成功',
|
||||||
|
duration: 1000,
|
||||||
|
})
|
||||||
|
} catch (e) {
|
||||||
|
wx.showToast({
|
||||||
|
icon: 'none',
|
||||||
|
title: '写数据失败',
|
||||||
|
duration: 2000,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
formReset(e) {
|
||||||
|
console.log(e)
|
||||||
|
this.setData({
|
||||||
|
inputText: '',
|
||||||
|
})
|
||||||
|
},
|
||||||
|
bindTextAreaBlur: function(e) {
|
||||||
|
console.log(e.detail.value)
|
||||||
|
this.setData({
|
||||||
|
inputText: e.detail.value,
|
||||||
|
})
|
||||||
|
},
|
||||||
|
chose: function() {
|
||||||
|
wx.chooseMessageFile({
|
||||||
|
count: 1,
|
||||||
|
type: 'file',
|
||||||
|
success: (res) => {
|
||||||
|
console.log(res)
|
||||||
|
fileName = res.tempFiles[0].name
|
||||||
|
wx.getFileSystemManager().readFile({
|
||||||
|
filePath: res.tempFiles[0].path,
|
||||||
|
// encoding: 'binary',
|
||||||
|
success: (res2) => {
|
||||||
|
console.log(res2)
|
||||||
|
fileBuffer = new Uint8Array(res2.data)
|
||||||
|
this.setData({
|
||||||
|
file_name: fileName,
|
||||||
|
file_size: res.tempFiles[0].size,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
writeData: async function(payload) {
|
||||||
|
console.log(`writeData len(${payload.byteLength})`, payload, new Date().toISOString())
|
||||||
|
if (!this.data.device.connected) {
|
||||||
|
wx.showModal({
|
||||||
|
title: '提示',
|
||||||
|
content: '蓝牙已断开,请重新连接设备',
|
||||||
|
showCancel: false,
|
||||||
|
success: function(res) {}
|
||||||
|
})
|
||||||
|
return Promise.reject(`ble disconnect`)
|
||||||
|
}
|
||||||
|
|
||||||
|
let pos = 0;
|
||||||
|
let bytes = payload.byteLength
|
||||||
|
while (bytes > 0) {
|
||||||
|
let frameBuffer;
|
||||||
|
if (bytes > 20) {
|
||||||
|
frameBuffer = payload.slice(pos, pos + 20);
|
||||||
|
pos += 20;
|
||||||
|
bytes -= 20;
|
||||||
|
} else {
|
||||||
|
frameBuffer = payload.slice(pos, pos + bytes);
|
||||||
|
pos += bytes;
|
||||||
|
bytes -= bytes;
|
||||||
|
}
|
||||||
|
// console.log(`frame(${frameBuffer.byteLength})`, frameBuffer)
|
||||||
|
try {
|
||||||
|
await bleapi.writeBLECharacteristicValue(
|
||||||
|
this.data.device.deviceId,
|
||||||
|
this.data.device.service.uuid,
|
||||||
|
this.data.device.characteristic.uuid,
|
||||||
|
frameBuffer
|
||||||
|
)
|
||||||
|
// Android手机,连续调用会存在写失败的可能性,建议加延时
|
||||||
|
await util.delayMs(20)
|
||||||
|
} catch (e) {
|
||||||
|
return Promise.reject(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Promise.resolve()
|
||||||
|
},
|
||||||
|
onMessage: function(data) {
|
||||||
|
let datastr = util.ab2strHex(data)
|
||||||
|
console.log('onMessage:', datastr, msgQueue, new Date().toISOString())
|
||||||
|
let str = util.ArrayBuffer2str(data)
|
||||||
|
this.setData({
|
||||||
|
receiveText: this.data.receiveText + str
|
||||||
|
})
|
||||||
|
|
||||||
|
if (DownLoadMode) { // 下载模式
|
||||||
|
let arr = new Uint8Array(data)
|
||||||
|
for (let i = 0; i < arr.length; i++) {
|
||||||
|
if (arr[i] == CAN) { // 取消传输
|
||||||
|
DownLoadMode = false
|
||||||
|
}
|
||||||
|
msgQueue.push(arr[i])
|
||||||
|
}
|
||||||
|
this.setData({
|
||||||
|
downloadLog: this.data.downloadLog + `${datastr}\r\n`
|
||||||
|
})
|
||||||
|
} else { // 普通模式
|
||||||
|
console.log("normal onReceive:", str)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
upload: async function() {
|
||||||
|
let that = this
|
||||||
|
if (!this.data.device.connected) {
|
||||||
|
wx.showModal({
|
||||||
|
title: '提示',
|
||||||
|
content: '蓝牙已断开',
|
||||||
|
showCancel: false,
|
||||||
|
success: function(res) {}
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!fileName || fileBuffer.length == 0) {
|
||||||
|
wx.showModal({
|
||||||
|
content: "请先选择固件",
|
||||||
|
showCancel: false
|
||||||
|
});
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
wx.showLoading({
|
||||||
|
title: '准备上传...',
|
||||||
|
mask: true,
|
||||||
|
})
|
||||||
|
this.setData({
|
||||||
|
send_percent: 0,
|
||||||
|
timestamp_start: "",
|
||||||
|
timestamp_end: "",
|
||||||
|
})
|
||||||
|
DownLoadMode = 1
|
||||||
|
console.log(`upload begin. bytes: ${fileBuffer.length} /1024: ${fileBuffer.length/1024}`)
|
||||||
|
try {
|
||||||
|
await this.recieveByte(C, SyncTimeout)
|
||||||
|
|
||||||
|
wx.showLoading({
|
||||||
|
title: '开始上传...',
|
||||||
|
mask: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
this.setData({
|
||||||
|
timestamp_start: util.formatTime(),
|
||||||
|
date_start: new Date()
|
||||||
|
})
|
||||||
|
|
||||||
|
console.log("send blockZero begin")
|
||||||
|
let id = 0
|
||||||
|
let blockZero = Packet.getNormalPacket(id, Packet.getZeroContent(fileName, fileBuffer.length))
|
||||||
|
await that.writeData(blockZero.buffer)
|
||||||
|
await that.recieveByte(ACK, RecvTimeout)
|
||||||
|
await that.recieveByte(C, SyncTimeout)
|
||||||
|
console.log("send blockZero complete")
|
||||||
|
wx.showLoading({
|
||||||
|
title: '上传中...',
|
||||||
|
mask: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
console.log("send file begin")
|
||||||
|
let nInterval = (bUse1K == true) ? 1024 : 128;
|
||||||
|
for (let i = 0; i < fileBuffer.length; i += nInterval) {
|
||||||
|
console.log("send block " + (i / nInterval + 1) + " start");
|
||||||
|
let upper = (fileBuffer.length < i + nInterval) ?
|
||||||
|
fileBuffer.length : i + nInterval;
|
||||||
|
let payloadBuf = new Uint8Array(nInterval);
|
||||||
|
for (let j = i; j < upper; j++) {
|
||||||
|
payloadBuf[j - i] = fileBuffer[j];
|
||||||
|
}
|
||||||
|
id = i / nInterval + 1;
|
||||||
|
let block = (bUse1K == true) ? Packet.getLongPacket(id, payloadBuf) : Packet.getNormalPacket(id, payloadBuf);
|
||||||
|
|
||||||
|
await that.writeData(block.buffer)
|
||||||
|
try {
|
||||||
|
// TODO: NAK 重传逻辑
|
||||||
|
await that.recieveByte(ACK, RecvTimeout)
|
||||||
|
} catch (e) {
|
||||||
|
console.error(`${(i / nInterval) + 1} block lost a ack`, e)
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("send block " + ((i / nInterval) + 1) + " succceed!");
|
||||||
|
this.setData({
|
||||||
|
send_percent: parseInt((i) / fileBuffer.length * 100),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
console.log("send file complete")
|
||||||
|
|
||||||
|
// EOT处理
|
||||||
|
// 超时,
|
||||||
|
// 返回 NAK, 则重传EOT
|
||||||
|
// 返回 ACK, 则结束
|
||||||
|
console.log("send EOT");
|
||||||
|
let sendEOTAndRetry = this.autoRetry(async function(buf) {
|
||||||
|
await that.writeData(new Uint8Array([EOT]).buffer)
|
||||||
|
console.log(`waiting ACK`, new Date().toISOString())
|
||||||
|
let c = await that.recieve(RecvTimeout)
|
||||||
|
if (c == ACK) {
|
||||||
|
console.log(`receive ACK 0x${c.toString(16)}`)
|
||||||
|
// 处理黏包的情况 [ACK, ACK]
|
||||||
|
while (msgQueue.length > 0) {
|
||||||
|
if (msgQueue[0] == ACK) {
|
||||||
|
let c = msgQueue.shift()
|
||||||
|
console.log(`shift ${c.toString(16)} from queue`, msgQueue)
|
||||||
|
} else {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (c == NAK) {
|
||||||
|
console.log(`receive NAK 0x${c.toString(16)}`)
|
||||||
|
// 处理黏包情况 [NAK, ACK, C]
|
||||||
|
if (msgQueue.length > 0) {
|
||||||
|
await that.recieveByte(ACK, RecvTimeout)
|
||||||
|
} else {
|
||||||
|
throw `receive NAK 0x${c.toString(16)}, resend EOT`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, 2)
|
||||||
|
await sendEOTAndRetry(new Uint8Array([EOT]).buffer)
|
||||||
|
await that.recieveByte(C, SyncTimeout)
|
||||||
|
|
||||||
|
console.log("send last block begin")
|
||||||
|
let blockLast = Packet.getNormalPacket(0, new Uint8Array(128));
|
||||||
|
await that.writeData(blockLast.buffer)
|
||||||
|
await that.recieveByte(ACK, RecvTimeout)
|
||||||
|
console.log("send last block compelte")
|
||||||
|
|
||||||
|
DownLoadMode = 0
|
||||||
|
let diff = new Date().getTime() - this.data.date_start.getTime()
|
||||||
|
let rate = Math.round(fileBuffer.length * 1000 / diff) * 100 / 100
|
||||||
|
console.log("upload complete")
|
||||||
|
this.setData({
|
||||||
|
send_percent: 100,
|
||||||
|
timestamp_end: util.formatTime(),
|
||||||
|
rate: rate,
|
||||||
|
})
|
||||||
|
wx.showModal({
|
||||||
|
content: "上传完成",
|
||||||
|
showCancel: false
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
DownLoadMode = 0
|
||||||
|
console.error("upload fail:", e)
|
||||||
|
wx.showModal({
|
||||||
|
content: "上传失败,请重试\r\n" + e,
|
||||||
|
showCancel: false
|
||||||
|
});
|
||||||
|
}
|
||||||
|
wx.hideLoading()
|
||||||
|
console.log("msgQueue", msgQueue)
|
||||||
|
msgQueue = [] // clear msgQueue
|
||||||
|
},
|
||||||
|
reciveByteAndRetry: async function(expect, timeout, retryNum) {
|
||||||
|
let func = this.autoRetry(this.recieveByte, retryNum)
|
||||||
|
await func(expect, timeout)
|
||||||
|
},
|
||||||
|
recieveByte: async function(expect, timeout) {
|
||||||
|
console.log(`waiting 0x${expect.toString(16)} for ${timeout}ms ...`, new Date().toISOString())
|
||||||
|
try {
|
||||||
|
let c = await this.recieve(timeout)
|
||||||
|
if (c == CAN) { // 接收端终止
|
||||||
|
throw `receive 0x${c.toString(16)}. cancel tranmission`
|
||||||
|
}
|
||||||
|
if (c != expect) {
|
||||||
|
throw `expect 0x${expect.toString(16)}, but recieve 0x${c.toString(16)}`
|
||||||
|
}
|
||||||
|
// 处理黏包的情况
|
||||||
|
while (msgQueue.length > 0) {
|
||||||
|
if (msgQueue[0] == expect) {
|
||||||
|
let c = msgQueue.shift()
|
||||||
|
console.log(`shift ${c.toString(16)} from queue`, msgQueue)
|
||||||
|
} else {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Promise.resolve(c)
|
||||||
|
} catch (e) {
|
||||||
|
throw `waiting 0x${expect.toString(16)} fail: ${e}`
|
||||||
|
}
|
||||||
|
},
|
||||||
|
recieve: async function(timeout) {
|
||||||
|
// console.log("start recieve", new Date().toISOString())
|
||||||
|
let elaspe = timeout
|
||||||
|
const interval = 500
|
||||||
|
while (elaspe > 0) {
|
||||||
|
elaspe = elaspe - interval
|
||||||
|
// console.log(`xxxx`, msgQueue.length, elaspe)
|
||||||
|
if (msgQueue.length > 0) {
|
||||||
|
let c = msgQueue.shift();
|
||||||
|
console.log(`receive from queue: ${c.toString(16)}`, msgQueue)
|
||||||
|
return Promise.resolve(c)
|
||||||
|
}
|
||||||
|
await util.delayMs(interval)
|
||||||
|
}
|
||||||
|
return Promise.reject(`receive timeout ${timeout}ms ${new Date().toISOString()}`)
|
||||||
|
},
|
||||||
|
/*
|
||||||
|
example:
|
||||||
|
let sendLastblockAndRetry = this.autoRetry(async function (buf) {
|
||||||
|
await that.writeData(buf);
|
||||||
|
await that.recieveByte(ACK, RecvTimeout)
|
||||||
|
}, 3)
|
||||||
|
await sendLastblockAndRetry(blockLast.buffer)
|
||||||
|
*/
|
||||||
|
autoRetry(func, retryMax) {
|
||||||
|
let retryNum = retryMax;
|
||||||
|
return async function funcR() {
|
||||||
|
let params = arguments
|
||||||
|
while (retryNum--) {
|
||||||
|
try {
|
||||||
|
await func(...params)
|
||||||
|
break
|
||||||
|
} catch (e) {
|
||||||
|
if (retryNum > 0) {
|
||||||
|
console.error(`retry ${retryMax - retryNum} time. error:`, e);
|
||||||
|
continue
|
||||||
|
} else {
|
||||||
|
throw e
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
RecvCleanTap: function() {
|
||||||
|
this.setData({
|
||||||
|
receiveText: ''
|
||||||
|
})
|
||||||
|
},
|
||||||
|
sendValue: async function(e) {
|
||||||
|
let val = e.currentTarget.dataset.val
|
||||||
|
try {
|
||||||
|
await this.writeData(util.str2abUint8(val))
|
||||||
|
wx.showToast({
|
||||||
|
icon: 'none',
|
||||||
|
title: '发送成功',
|
||||||
|
duration: 1000,
|
||||||
|
})
|
||||||
|
} catch (e) {
|
||||||
|
wx.showToast({
|
||||||
|
icon: 'none',
|
||||||
|
title: '发送失败',
|
||||||
|
duration: 2000,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"usingComponents": {}
|
||||||
|
}
|
@@ -0,0 +1,94 @@
|
|||||||
|
<!--pages/ble-device/index.wxml-->
|
||||||
|
|
||||||
|
<template name="connectStatus">
|
||||||
|
<view wx:if="{{ connected }}">
|
||||||
|
<view style="color:green; font-weight:bold">已连接</view>
|
||||||
|
</view>
|
||||||
|
<view wx:elif="{{ !connected }}">
|
||||||
|
<view style="color:#ff0000; font-weight:bold">已断开</view>
|
||||||
|
</view>
|
||||||
|
<view wx:else>
|
||||||
|
<view style="color:#ff0000; font-weight:bold">未连接</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<view class="container">
|
||||||
|
<view class="title">
|
||||||
|
设备信息
|
||||||
|
</view>
|
||||||
|
<view class="box">
|
||||||
|
<view class="box-cell flex-row space-between">
|
||||||
|
<view>设备名称</view>
|
||||||
|
<view>{{device.name}}</view>
|
||||||
|
</view>
|
||||||
|
<view class="box-cell flex-row space-between">
|
||||||
|
<view>设备ID</view>
|
||||||
|
<view>{{device.deviceId}}</view>
|
||||||
|
</view>
|
||||||
|
<view class="box-cell flex-row space-between">
|
||||||
|
<view>连接状态</view>
|
||||||
|
<view>
|
||||||
|
<template is="connectStatus" data="{{connected:device.connected}}" />
|
||||||
|
</view>
|
||||||
|
<!-- <view>{{device.connected}}</view> -->
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="flex-row space-between">
|
||||||
|
<view class="title">
|
||||||
|
串口输出
|
||||||
|
</view>
|
||||||
|
<view>
|
||||||
|
<button size="mini" type="default" bindtap="RecvCleanTap">清空</button>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<textarea class="revc_text" maxlength="-1" disabled value="{{receiveText}}" />
|
||||||
|
|
||||||
|
<view class="title">
|
||||||
|
固件升级
|
||||||
|
</view>
|
||||||
|
<view class="box">
|
||||||
|
<view class="box-cell flex-row space-between">
|
||||||
|
<view>菜单选项</view>
|
||||||
|
<view>
|
||||||
|
<button class="menu-item" size="mini" type="default" data-val="1" bindtap="sendValue">1</button>
|
||||||
|
<button class="menu-item" size="mini" type="default" data-val="2" bindtap="sendValue">2</button>
|
||||||
|
<button class="menu-item" size="mini" type="default" data-val="3" bindtap="sendValue">3</button>
|
||||||
|
<button class="menu-item" size="mini" type="default" data-val="4" bindtap="sendValue">4</button>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view class="box-cell flex-row space-between">
|
||||||
|
<view>
|
||||||
|
固件升级
|
||||||
|
</view>
|
||||||
|
<view>
|
||||||
|
<button class="menu-item" size="mini" type="default" bindtap="chose">选择固件</button>
|
||||||
|
<button class="menu-item" size="mini" type="default" bindtap="upload">上传固件</button>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view wx:if="{{file_name}}">
|
||||||
|
<view class="box-cell flex-row space-between">
|
||||||
|
<view>文件名(大小)</view>
|
||||||
|
<view>{{file_name}} ({{file_size}})</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view wx:if="{{timestamp_start}}">
|
||||||
|
<progress class="" percent="{{send_percent}}" show-info border-radius="10" stroke-width="5" />
|
||||||
|
</view>
|
||||||
|
<view wx:if="{{timestamp_end}}">
|
||||||
|
<view class="box-cell flex-column">
|
||||||
|
<view>begin@{{timestamp_start}}</view>
|
||||||
|
<view>finish@{{timestamp_end}}</view>
|
||||||
|
<view>rate@{{rate}}(B/s)</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- <form catchsubmit="formSubmit" catchreset="formReset">
|
||||||
|
<textarea class="send_text" name="textarea" maxlength="-1" value="{{inputText}}" />
|
||||||
|
<view class="btn-area flex-row space-between">
|
||||||
|
<button size="mini" type="default" formType="reset">清空</button>
|
||||||
|
<button size="mini" type="primary" formType="submit">发送</button>
|
||||||
|
</view>
|
||||||
|
</form> -->
|
||||||
|
</view>
|
@@ -0,0 +1,78 @@
|
|||||||
|
page {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
position: relative;
|
||||||
|
color: #333;
|
||||||
|
background-color: #F8F8F8;
|
||||||
|
font-size: 16px;
|
||||||
|
/*font-family: -apple-system-font,Helvetica Neue,Helvetica,sans-serif;*/
|
||||||
|
font-family: PingFang SC, Helvetica Neue, Hiragino Sans GB, Helvetica, Microsoft YaHei, Arial;
|
||||||
|
-ms-text-size-adjust: 100%;
|
||||||
|
-webkit-text-size-adjust: 100%;
|
||||||
|
-webkit-tap-highlight-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
margin: 20rpx 30rpx
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
font-size: 32rpx;
|
||||||
|
font-weight: bold;
|
||||||
|
margin-bottom: 10rpx;
|
||||||
|
padding-left: 20rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.box {
|
||||||
|
margin-bottom: 30rpx;
|
||||||
|
padding: 20rpx;
|
||||||
|
border: 2rpx solid #d9d9d9;
|
||||||
|
border-radius: 20rpx;
|
||||||
|
background: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.box-cell {
|
||||||
|
padding-bottom: 10rpx;
|
||||||
|
line-height: 50rpx;
|
||||||
|
font-size: 32rpx;
|
||||||
|
color: #666;
|
||||||
|
/* border-bottom: 2rpx solid #d9d9d9; */
|
||||||
|
}
|
||||||
|
|
||||||
|
.send_text {
|
||||||
|
height: 100rpx;
|
||||||
|
width: 90%;
|
||||||
|
border: 2px solid #39beff;
|
||||||
|
margin: 10px auto;
|
||||||
|
padding: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file_name {
|
||||||
|
float: left;
|
||||||
|
padding-left: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file_size {
|
||||||
|
float: left;
|
||||||
|
padding-left: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.revc_text {
|
||||||
|
width: 100%;
|
||||||
|
height: 400rpx;
|
||||||
|
border: 1rpx solid grey;
|
||||||
|
/* margin: 0 auto 10px; */
|
||||||
|
padding: 5rpx;
|
||||||
|
/* padding-bottom: 10rpx; */
|
||||||
|
margin-bottom: 20rpx;
|
||||||
|
font-size: 25rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-item {
|
||||||
|
margin-left: 20rpx
|
||||||
|
}
|
@@ -0,0 +1,129 @@
|
|||||||
|
const app = getApp()
|
||||||
|
let util = require('../../../../utils/util.js');
|
||||||
|
let bleapi = require('../../../../utils/hardware/ble/ble-api.js');
|
||||||
|
|
||||||
|
Page({
|
||||||
|
data: {
|
||||||
|
searching: false,
|
||||||
|
devicesList: []
|
||||||
|
},
|
||||||
|
onLoad: async function (options) {
|
||||||
|
this.search()
|
||||||
|
},
|
||||||
|
onUnload: async function () {
|
||||||
|
bleapi.closeBluetoothAdapter()
|
||||||
|
},
|
||||||
|
onBluetoothDeviceFound() {
|
||||||
|
let that = this
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
wx.onBluetoothDeviceFound(function (res) {
|
||||||
|
var name = res.devices[0].name
|
||||||
|
if (name) {
|
||||||
|
console.log("onBluetoothDeviceFound:", name, res);
|
||||||
|
that.data.devicesList.push(res.devices[0])
|
||||||
|
that.setData({
|
||||||
|
devicesList: that.data.devicesList
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
resolve()
|
||||||
|
console.log("onBluetoothDeviceFound start")
|
||||||
|
})
|
||||||
|
},
|
||||||
|
search: async function() {
|
||||||
|
try {
|
||||||
|
await bleapi.closeBluetoothAdapter()
|
||||||
|
await bleapi.openBluetoothAdapter()
|
||||||
|
} catch(e) {
|
||||||
|
wx.showModal({
|
||||||
|
content: "请检查手机蓝牙是否打开",
|
||||||
|
showCancel: false
|
||||||
|
});
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 开始扫描蓝牙设备
|
||||||
|
await bleapi.startBluetoothDevicesDiscovery([])
|
||||||
|
this.setData({
|
||||||
|
searching: true,
|
||||||
|
devicesList: []
|
||||||
|
})
|
||||||
|
await this.onBluetoothDeviceFound()
|
||||||
|
|
||||||
|
// 每次扫描蓝牙设备10秒
|
||||||
|
await util.delayMs(10000)
|
||||||
|
if (this.data.searching) {
|
||||||
|
this.setData({
|
||||||
|
searching: false
|
||||||
|
})
|
||||||
|
await bleapi.stopBluetoothDevicesDiscovery()
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e)
|
||||||
|
wx.showModal({
|
||||||
|
content: "搜索设备失败\n" + e,
|
||||||
|
showCancel: false
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
connect: async function(e) {
|
||||||
|
console.log(e.currentTarget)
|
||||||
|
wx.showLoading({
|
||||||
|
title: '连接蓝牙设备中...',
|
||||||
|
})
|
||||||
|
let deviceId = e.currentTarget.id
|
||||||
|
let deviceName = e.currentTarget.dataset.name
|
||||||
|
|
||||||
|
try {
|
||||||
|
await bleapi.stopBluetoothDevicesDiscovery()
|
||||||
|
await bleapi.closeBLEConnection(deviceId)
|
||||||
|
|
||||||
|
// 创建BLE连接
|
||||||
|
await bleapi.createBLEConnection(deviceId)
|
||||||
|
|
||||||
|
// 读取BLE设备的 Services
|
||||||
|
let service = null
|
||||||
|
let services = await bleapi.getBLEDeviceServices(deviceId)
|
||||||
|
let serviceId = '0000FFE0-0000-1000-8000-00805F9B34FB' // 指定ServiceID
|
||||||
|
for (let i = 0; i < services.length; i++) {
|
||||||
|
if (services[i].isPrimary && services[i].uuid == serviceId) {
|
||||||
|
service = services[i]
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 读取BLE设备指定 ServiceId 的 Characteristics
|
||||||
|
let characteristics = await bleapi.getBLEDeviceCharacteristics(deviceId, service.uuid)
|
||||||
|
let characteristic = characteristics[0] // 默认选择第一个特征值
|
||||||
|
console.log('characteristic', characteristic)
|
||||||
|
|
||||||
|
// 完成连接BLE设备,跳转到该设备页面
|
||||||
|
let device = {
|
||||||
|
connected: true,
|
||||||
|
name: deviceName,
|
||||||
|
deviceId: deviceId,
|
||||||
|
service: service,
|
||||||
|
characteristic: characteristic,
|
||||||
|
services: services,
|
||||||
|
characteristics: characteristics,
|
||||||
|
}
|
||||||
|
console.log(device)
|
||||||
|
wx.hideLoading()
|
||||||
|
wx.navigateTo({
|
||||||
|
url: '../device/index',
|
||||||
|
success: function(res) {
|
||||||
|
res.eventChannel.emit('eventData', {
|
||||||
|
device: device,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e)
|
||||||
|
wx.showToast({
|
||||||
|
title: "连接蓝牙失败,请重试",
|
||||||
|
icon: "none"
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"usingComponents": {}
|
||||||
|
}
|
@@ -0,0 +1,15 @@
|
|||||||
|
<view class="container">
|
||||||
|
<scroll-view scroll-y style="width:690rpx;height:{{list_height}}rpx">
|
||||||
|
<block wx:for="{{devicesList}}" wx:key="deviceId">
|
||||||
|
<view class="list-item" id="{{item.deviceId}}" data-name="{{item.name}}" bindtap="connect">
|
||||||
|
<view style="display:flex;flex-direction:column;width:80%">
|
||||||
|
<text style="font-size:medium;word-break:break-all">设备名称: {{item.name}}</text>
|
||||||
|
<text style="font-size:x-small;color:gray;word-break:break-all">设备ID: {{item.deviceId}}</text>
|
||||||
|
<text style="font-size:x-small;color:gray;word-break:break-all">信号强度RSSI: {{item.RSSI}}</text>
|
||||||
|
</view>
|
||||||
|
<image style="width:36px;height:36px" mode="aspectFit" src="/images/bluetooth.png"></image>
|
||||||
|
</view>
|
||||||
|
</block>
|
||||||
|
</scroll-view>
|
||||||
|
<button type="primary" class="button" loading="{{searching}}" bindtap="search">{{searching?"搜索中...":"搜索蓝牙设备"}}</button>
|
||||||
|
</view>
|
@@ -0,0 +1,27 @@
|
|||||||
|
page {
|
||||||
|
background-color: #f8f8f8;
|
||||||
|
}
|
||||||
|
.container {
|
||||||
|
padding: 0 30rpx 0 30rpx;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
.list-item {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
width: 100%;
|
||||||
|
padding: 10px 0 10px 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
border: 1px solid #000;
|
||||||
|
border-style: none none solid none;
|
||||||
|
border-bottom-color: lightgray;
|
||||||
|
}
|
||||||
|
.list-item:last-child {
|
||||||
|
border-style: none;
|
||||||
|
}
|
||||||
|
.button {
|
||||||
|
position: fixed;
|
||||||
|
width: 690rpx;
|
||||||
|
bottom: 30rpx;
|
||||||
|
}
|
7
tools/Mini_Program/iap/miniprogram/sitemap.json
Normal file
7
tools/Mini_Program/iap/miniprogram/sitemap.json
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"desc": "关于本文件的更多信息,请参考文档 https://developers.weixin.qq.com/miniprogram/dev/framework/sitemap.html",
|
||||||
|
"rules": [{
|
||||||
|
"action": "allow",
|
||||||
|
"page": "*"
|
||||||
|
}]
|
||||||
|
}
|
554
tools/Mini_Program/iap/miniprogram/utils/hardware/ble/ble-api.js
Normal file
554
tools/Mini_Program/iap/miniprogram/utils/hardware/ble/ble-api.js
Normal file
@@ -0,0 +1,554 @@
|
|||||||
|
/**
|
||||||
|
* 初始化蓝牙模块
|
||||||
|
*
|
||||||
|
* @return {Object} 调用结果
|
||||||
|
*/
|
||||||
|
function openBluetoothAdapter() {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
wx.openBluetoothAdapter({
|
||||||
|
success(res) {
|
||||||
|
resolve(res)
|
||||||
|
},
|
||||||
|
fail(res) {
|
||||||
|
reject(res)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 关闭蓝牙模块
|
||||||
|
*
|
||||||
|
* @return {Object} 调用结果
|
||||||
|
*/
|
||||||
|
function closeBluetoothAdapter() {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
wx.closeBluetoothAdapter({
|
||||||
|
complete(res) {
|
||||||
|
resolve(res)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 监听蓝牙状态
|
||||||
|
*
|
||||||
|
* @param {function} onBluetoothStateChange 函数对象,监听蓝牙状态变化的回调函数
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
function onBluetoothAdapterStateChange(onBluetoothStateChange) {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
wx.onBluetoothAdapterStateChange(function(res) {
|
||||||
|
// console.log('onBluetoothAdapterStateChange, now is', res)
|
||||||
|
onBluetoothStateChange(res)
|
||||||
|
})
|
||||||
|
resolve()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 启动搜索蓝牙设备功能
|
||||||
|
*
|
||||||
|
* @param {string} services 蓝牙服务
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
function startBluetoothDevicesDiscovery(services) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
wx.startBluetoothDevicesDiscovery({
|
||||||
|
services: services || [],
|
||||||
|
allowDuplicatesKey: false,
|
||||||
|
success(res) {
|
||||||
|
console.log("wx.startBluetoothDevicesDiscovery success")
|
||||||
|
resolve(res)
|
||||||
|
},
|
||||||
|
fail(res) {
|
||||||
|
console.log("wx.startBluetoothDevicesDiscovery fail")
|
||||||
|
reject(res)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 关闭搜索蓝牙设备功能
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
function stopBluetoothDevicesDiscovery() {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
wx.stopBluetoothDevicesDiscovery({
|
||||||
|
complete(res) {
|
||||||
|
console.log("wx.stopBluetoothDevicesDiscovery success")
|
||||||
|
resolve()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 断开与低功耗蓝牙设备的连接
|
||||||
|
*
|
||||||
|
* @param {string} deviceId 蓝牙设备ID
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
function closeBLEConnection(deviceId) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
wx.closeBLEConnection({
|
||||||
|
deviceId,
|
||||||
|
success(res) {
|
||||||
|
console.log(`closeBLEConnection ${deviceId} success`, res)
|
||||||
|
},
|
||||||
|
fail(res) {
|
||||||
|
console.log(`closeBLEConnection ${deviceId} fail`, res)
|
||||||
|
},
|
||||||
|
complete(res) {
|
||||||
|
resolve()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 连接低功耗蓝牙设备
|
||||||
|
*
|
||||||
|
* @param {string} deviceId 蓝牙设备ID
|
||||||
|
* @return {Object} 调用结果
|
||||||
|
*/
|
||||||
|
function createBLEConnection(deviceId) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
wx.createBLEConnection({
|
||||||
|
deviceId: deviceId,
|
||||||
|
timeout: 5000, // TO_CHECK: 设置连接蓝牙超时时间
|
||||||
|
success: function(res) {
|
||||||
|
console.log(`createBLEConnection ${deviceId} success`, res)
|
||||||
|
resolve(res)
|
||||||
|
},
|
||||||
|
fail: function(res) {
|
||||||
|
console.log(`createBLEConnection ${deviceId} fail`, res);
|
||||||
|
reject(new Error(res.errMsg || 'wx.createBLEConnection fail'))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 监听低功耗蓝牙连接状态的改变事件
|
||||||
|
*
|
||||||
|
* @param {function} onStateChange 函数对象,连接状态变化的回调函数
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
function onBLEConnectionStateChange(onStateChange) {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
wx.onBLEConnectionStateChange(function(res) { // 该方法回调中可以用于处理连接意外断开等异常情况
|
||||||
|
// console.log(`onBLEConnectionStateChange device ${res.deviceId} state has changed, connected: ${res.connected}`)
|
||||||
|
onStateChange(res)
|
||||||
|
})
|
||||||
|
resolve()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取蓝牙设备所有服务
|
||||||
|
*
|
||||||
|
* @param {string} deviceId 蓝牙设备ID
|
||||||
|
* @return {Object} 调用结果
|
||||||
|
*/
|
||||||
|
function getBLEDeviceServices(deviceId) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
wx.getBLEDeviceServices({
|
||||||
|
deviceId: deviceId,
|
||||||
|
success: function(res) {
|
||||||
|
console.log(`wx.getBLEDeviceServices ${deviceId} success:`, res.services)
|
||||||
|
resolve(res.services)
|
||||||
|
},
|
||||||
|
fail(res) { // 获取蓝牙服务失败
|
||||||
|
console.log(`wx.getBLEDeviceServices ${deviceId} fail:`, res)
|
||||||
|
reject(res.errMsg || 'wx.getBLEDeviceServices fail')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取蓝牙设备某个服务中所有特征值
|
||||||
|
*
|
||||||
|
* @param {string} deviceId 蓝牙设备ID
|
||||||
|
* @param {string} serviceId 蓝牙服务ID
|
||||||
|
* @return {Object} 调用结果
|
||||||
|
*/
|
||||||
|
function getBLEDeviceCharacteristics(deviceId, serviceId) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
wx.getBLEDeviceCharacteristics({
|
||||||
|
deviceId: deviceId,
|
||||||
|
serviceId: serviceId,
|
||||||
|
success(res) {
|
||||||
|
console.log(`wx.getBLEDeviceCharacteristics ${deviceId} ${serviceId} success:`, res.characteristics)
|
||||||
|
resolve(res.characteristics)
|
||||||
|
},
|
||||||
|
fail(res) { // 读取蓝牙特征值失败
|
||||||
|
console.log(`wx.getBLEDeviceCharacteristics ${deviceId} ${serviceId} fail`, res)
|
||||||
|
reject(res.errMsg || 'wx.getBLEDeviceCharacteristics fail')
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 启用低功耗蓝牙设备特征值变化时的 notify 功能,订阅特征值
|
||||||
|
* 注:必须设备的特征值支持 notify 或者 indicate 才可以成功调用
|
||||||
|
*
|
||||||
|
* @param {string} deviceId 蓝牙设备ID
|
||||||
|
* @param {string} serviceId 蓝牙服务ID
|
||||||
|
* @param {string} notifyCharacteristicId 蓝牙特征值ID
|
||||||
|
* @return {Object} 调用结果
|
||||||
|
*/
|
||||||
|
function notifyBLECharacteristicValueChanged(deviceId, serviceId, notifyCharacteristicId) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
wx.notifyBLECharacteristicValueChanged({
|
||||||
|
deviceId: deviceId,
|
||||||
|
serviceId: serviceId,
|
||||||
|
characteristicId: notifyCharacteristicId,
|
||||||
|
state: true,
|
||||||
|
success(res) {
|
||||||
|
console.log(`wx.notifyBLECharacteristicValueChanged ${deviceId} ${serviceId} ${notifyCharacteristicId} success`);
|
||||||
|
resolve(true)
|
||||||
|
},
|
||||||
|
fail(err) {
|
||||||
|
console.log(`wx.notifyBLECharacteristicValueChanged ${deviceId} ${serviceId} ${notifyCharacteristicId} fail`, err);
|
||||||
|
reject("启用蓝牙特征值notify功能失败")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 监听低功耗蓝牙设备的特征值变化事件
|
||||||
|
* 注:必须先启用 notifyBLECharacteristicValueChange 接口才能接收到设备推送的 notification
|
||||||
|
*
|
||||||
|
* @param {function} onMessage 函数对象,读数据的回调函数
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
function onBLECharacteristicValueChange(onMessage) {
|
||||||
|
var receivedData = null
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
wx.onBLECharacteristicValueChange(function(res) {
|
||||||
|
//{value: ArrayBuffer, deviceId: "D8:00:D2:4F:24:17", serviceId: "ba11f08c-5f14-0b0d-1080-007cbe238851-0x600000460240", characteristicId: "0000cd04-0000-1000-8000-00805f9b34fb-0x60800069fb80"}
|
||||||
|
onMessage(res.value)
|
||||||
|
})
|
||||||
|
resolve()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 向低功耗蓝牙设备特征值中写入二进制数据
|
||||||
|
* 注:必须设备的特征值支持 write 才可以成功调用
|
||||||
|
*
|
||||||
|
* @param {string} deviceId 蓝牙设备ID
|
||||||
|
* @param {string} serviceId 蓝牙服务ID
|
||||||
|
* @param {string} writeCharacteristicId 蓝牙特征值ID
|
||||||
|
* @param {string} payload 数据,必须是ArrayBuffer类型
|
||||||
|
* @return {Object} 调用结果
|
||||||
|
*/
|
||||||
|
function writeBLECharacteristicValue(deviceId, serviceId, writeCharacteristicId, payload) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
wx.writeBLECharacteristicValue({
|
||||||
|
deviceId: deviceId,
|
||||||
|
serviceId: serviceId,
|
||||||
|
characteristicId: writeCharacteristicId,
|
||||||
|
value: payload, // parameter.value should be ArrayBuffer
|
||||||
|
fail(res) {
|
||||||
|
console.log(`writeBLECharacteristicValue fail: ${deviceId} ${serviceId} ${writeCharacteristicId}`, res)
|
||||||
|
reject(res)
|
||||||
|
},
|
||||||
|
success(res) {
|
||||||
|
resolve(res)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 读取低功耗蓝牙设备的特征值的二进制数据值。
|
||||||
|
* 注:必须设备的特征值支持 read 才可以成功调用; 接口读取到的信息需要在 onBLECharacteristicValueChange 方法注册的回调中获取
|
||||||
|
*
|
||||||
|
* @param {string} deviceId 蓝牙设备ID
|
||||||
|
* @param {string} serviceId 蓝牙服务ID
|
||||||
|
* @param {string} readCharacteristicId 蓝牙特征值ID
|
||||||
|
* @return {Object} 调用结果
|
||||||
|
*/
|
||||||
|
function readBLECharacteristicValue(deviceId, serviceId, readCharacteristicId) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
wx.readBLECharacteristicValue({
|
||||||
|
deviceId: deviceId,
|
||||||
|
serviceId: serviceId,
|
||||||
|
characteristicId: readCharacteristicId,
|
||||||
|
fail(res) {
|
||||||
|
console.log(`readBLECharacteristicValue fail: ${deviceId} ${serviceId} ${readCharacteristicId}`, res)
|
||||||
|
reject(res)
|
||||||
|
},
|
||||||
|
success(res) {
|
||||||
|
resolve(res)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 连接设备
|
||||||
|
*
|
||||||
|
* @param {string} deviceId 蓝牙设备ID
|
||||||
|
* @return {Object} 设备连接信息
|
||||||
|
*/
|
||||||
|
function connectDevice(deviceId) {
|
||||||
|
return closeBLEConnection(deviceId).then(() => {
|
||||||
|
// 断开重连需要间隔1s,否则会出现假连接成功
|
||||||
|
// 还有一种方式是 getConnectedBluetoothDevices,对比已连接的蓝牙,如果已连接直接跳到设备页面
|
||||||
|
return createBLEConnection(deviceId)
|
||||||
|
}).then(() => {
|
||||||
|
// 读取 BLE services
|
||||||
|
return getBLEDeviceServices(deviceId)
|
||||||
|
}).then(([deviceId, services]) => {
|
||||||
|
// 读取 BLE Characteristics
|
||||||
|
for (let i = 0; i < services.length; i++) {
|
||||||
|
if (services[i].isPrimary) { // 确定对应服务。这里使用第一个primary服务
|
||||||
|
let serviceId = services[i].uuid
|
||||||
|
return getBLEDeviceCharacteristics(deviceId, serviceId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Promise.reject(new Error('未找到蓝牙主服务'))
|
||||||
|
}).then(
|
||||||
|
// 必须先启用 notifyBLECharacteristicValueChange 才能监听到设备 onBLECharacteristicValueChange 事件
|
||||||
|
([deviceId, serviceId, characteristics]) => {
|
||||||
|
let notifyCharacteristicId = null
|
||||||
|
let writeCharacteristicId = null
|
||||||
|
let readCharacteristicId = null
|
||||||
|
characteristics.forEach(function(item, index) {
|
||||||
|
if (item.properties.notify == true) {
|
||||||
|
notifyCharacteristicId = item.uuid
|
||||||
|
notifyBLECharacteristicValueChanged(deviceId, serviceId, item.uuid)
|
||||||
|
}
|
||||||
|
if (item.properties.write == true) {
|
||||||
|
writeCharacteristicId = item.uuid
|
||||||
|
}
|
||||||
|
if (item.properties.read == true) {
|
||||||
|
readCharacteristicId = item.uuid
|
||||||
|
}
|
||||||
|
})
|
||||||
|
let device = {
|
||||||
|
connected: true,
|
||||||
|
deviceId: deviceId,
|
||||||
|
serviceId: serviceId,
|
||||||
|
notifyCharacteristicId: notifyCharacteristicId,
|
||||||
|
writeCharacteristicId: writeCharacteristicId,
|
||||||
|
readCharacteristicId: readCharacteristicId,
|
||||||
|
}
|
||||||
|
return Promise.resolve(device)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据名称搜索设备
|
||||||
|
*
|
||||||
|
* @param {string} name 蓝牙设备名称
|
||||||
|
* @param {string} timeout 搜索超时时间,单位ms
|
||||||
|
* @param {array} services 蓝牙设备的服务ID
|
||||||
|
* @return {string} deviceId 蓝牙设备ID
|
||||||
|
*/
|
||||||
|
function searchDeviceName(name, timeout, services = ["6E400001-B5A3-F393-E0A9-E50E24DCCA9E"]) {
|
||||||
|
console.log("search begin", name)
|
||||||
|
let timer = null
|
||||||
|
let searchTimeout = new Promise((resolve, reject) => {
|
||||||
|
timer = setTimeout(function() {
|
||||||
|
console.log("searchDeviceName timeout", name)
|
||||||
|
stopBluetoothDevicesDiscovery()
|
||||||
|
reject(null)
|
||||||
|
}, timeout);
|
||||||
|
})
|
||||||
|
|
||||||
|
let search = Promise.resolve()
|
||||||
|
.then(() => {
|
||||||
|
return closeBluetoothAdapter()
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
return openBluetoothAdapter()
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
return startBluetoothDevicesDiscovery(services)
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
console.log("wx.onBluetoothDeviceFound start")
|
||||||
|
wx.onBluetoothDeviceFound(function(res) {
|
||||||
|
let foundName = res.devices[0].localName // TODO: localName or name?
|
||||||
|
if (foundName && foundName == name) {
|
||||||
|
console.log("searchDeviceName found:", name, res);
|
||||||
|
wx.offBluetoothDeviceFound(this)
|
||||||
|
clearTimeout(timer)
|
||||||
|
stopBluetoothDevicesDiscovery()
|
||||||
|
resolve(res.devices[0].deviceId)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
return Promise.race([search, searchTimeout])
|
||||||
|
}
|
||||||
|
|
||||||
|
async function searchDevices(name, timeout, services = ["6E400001-B5A3-F393-E0A9-E50E24DCCA9E"]) {
|
||||||
|
console.log("searchDevices", name)
|
||||||
|
let timer = null
|
||||||
|
let searchTimeout = new Promise((resolve, reject) => {
|
||||||
|
timer = setTimeout(function() {
|
||||||
|
console.log("searchDevices timeout", name)
|
||||||
|
stopBluetoothDevicesDiscovery()
|
||||||
|
reject(null)
|
||||||
|
}, timeout);
|
||||||
|
})
|
||||||
|
|
||||||
|
let search = Promise.resolve()
|
||||||
|
.then(() => {
|
||||||
|
return closeBluetoothAdapter()
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
return openBluetoothAdapter()
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
return startBluetoothDevicesDiscovery(services)
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
console.log("wx.onBluetoothDeviceFound start")
|
||||||
|
wx.onBluetoothDeviceFound(function(res) {
|
||||||
|
let foundName = res.devices[0].localName // TODO: localName or name?
|
||||||
|
if (foundName && foundName == name) {
|
||||||
|
console.log("searchDeviceName found:", name, res);
|
||||||
|
wx.offBluetoothDeviceFound(this)
|
||||||
|
clearTimeout(timer)
|
||||||
|
stopBluetoothDevicesDiscovery()
|
||||||
|
resolve(res.devices[0].deviceId)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
return Promise.race([search, searchTimeout])
|
||||||
|
}
|
||||||
|
|
||||||
|
// function searchAndConnect(name, timeout, services = ["6E400001-B5A3-F393-E0A9-E50E24DCCA9E"]) {
|
||||||
|
function searchAndConnect(name, timeout, services = []) {
|
||||||
|
return searchDeviceName(name, timeout, services).then(res => {
|
||||||
|
let devicesList = res
|
||||||
|
if (devicesList.length == 0) {
|
||||||
|
return Promise.reject(false)
|
||||||
|
}
|
||||||
|
let deviceId = devicesList[0].deviceId
|
||||||
|
return connectDevice(deviceId)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 扫描设备
|
||||||
|
function scanDevicesForSometime(services, ms) {
|
||||||
|
startBluetoothDevicesDiscovery(services).then(res => {
|
||||||
|
console.log("wx.startBluetoothDevicesDiscovery success", res)
|
||||||
|
})
|
||||||
|
setTimeout(function() {
|
||||||
|
wx.stopBluetoothDevicesDiscovery({
|
||||||
|
success(res) {
|
||||||
|
console.log("wx.stopBluetoothDevicesDiscovery success", res)
|
||||||
|
},
|
||||||
|
fail(e) {
|
||||||
|
console.log("wx.stopBluetoothDevicesDiscovery fail", res)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}, ms);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 扫描设备
|
||||||
|
function connectFailAnalysis(deviceId) {
|
||||||
|
console.log("scanDevices ...")
|
||||||
|
let that = this
|
||||||
|
let found = false
|
||||||
|
let discovery = Promise.resolve().then(
|
||||||
|
() => {
|
||||||
|
return openBluetoothAdapter()
|
||||||
|
}
|
||||||
|
).then(
|
||||||
|
() => {
|
||||||
|
return startBluetoothDevicesDiscovery([])
|
||||||
|
// return startBluetoothDevicesDiscovery(["6E400001-B5A3-F393-E0A9-E50E24DCCA9E"])
|
||||||
|
}
|
||||||
|
).then(
|
||||||
|
() => {
|
||||||
|
let devicesList = []
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
wx.onBluetoothDeviceFound(function(res) {
|
||||||
|
let name = res.devices[0].name
|
||||||
|
if (name != "") {
|
||||||
|
console.log("onBluetoothDeviceFound:", name, res);
|
||||||
|
devicesList.push(res.devices[0])
|
||||||
|
let foundDeviceId = res.devices[0].deviceId
|
||||||
|
console.log("found", foundDeviceId, deviceId)
|
||||||
|
// 尝试连接,读取等
|
||||||
|
if (foundDeviceId = deviceId) {
|
||||||
|
found = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
resolve()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
let discoveryTimeout = new Promise(resolve => {
|
||||||
|
setTimeout(resolve, 10000);
|
||||||
|
}).then(res => {
|
||||||
|
console.log("discoveryTimeout")
|
||||||
|
return stopBluetoothDevicesDiscovery()
|
||||||
|
}).then(res => {
|
||||||
|
return new Promise(res => {
|
||||||
|
console.log("xxxx", found)
|
||||||
|
if (found) {
|
||||||
|
console.log("found", deviceId)
|
||||||
|
resolve(true)
|
||||||
|
} else {
|
||||||
|
console.log("not found", deviceId)
|
||||||
|
reject(false)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
return Promise.race([discovery, discoveryTimeout])
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
// 蓝牙适配器
|
||||||
|
closeBluetoothAdapter: closeBluetoothAdapter,
|
||||||
|
openBluetoothAdapter: openBluetoothAdapter,
|
||||||
|
onBluetoothAdapterStateChange: onBluetoothAdapterStateChange,
|
||||||
|
startBluetoothDevicesDiscovery: startBluetoothDevicesDiscovery,
|
||||||
|
stopBluetoothDevicesDiscovery: stopBluetoothDevicesDiscovery,
|
||||||
|
|
||||||
|
// BLE连接
|
||||||
|
closeBLEConnection: closeBLEConnection,
|
||||||
|
createBLEConnection: createBLEConnection,
|
||||||
|
onBLEConnectionStateChange: onBLEConnectionStateChange,
|
||||||
|
|
||||||
|
// BLE服务和特征值
|
||||||
|
getBLEDeviceServices: getBLEDeviceServices,
|
||||||
|
getBLEDeviceCharacteristics: getBLEDeviceCharacteristics,
|
||||||
|
notifyBLECharacteristicValueChanged: notifyBLECharacteristicValueChanged,
|
||||||
|
onBLECharacteristicValueChange: onBLECharacteristicValueChange,
|
||||||
|
|
||||||
|
// BLE读写
|
||||||
|
writeBLECharacteristicValue: writeBLECharacteristicValue,
|
||||||
|
readBLECharacteristicValue: readBLECharacteristicValue,
|
||||||
|
|
||||||
|
// wrapper
|
||||||
|
scanDevicesForSometime: scanDevicesForSometime,
|
||||||
|
searchDeviceName: searchDeviceName,
|
||||||
|
connectDevice: connectDevice,
|
||||||
|
searchAndConnect: searchAndConnect,
|
||||||
|
connectFailAnalysis: connectFailAnalysis,
|
||||||
|
}
|
@@ -0,0 +1,83 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
function UpdateCRC16(crcIn, byte) {
|
||||||
|
let crc = crcIn;
|
||||||
|
let inNum = byte | 0x100;
|
||||||
|
do {
|
||||||
|
crc <<= 1;
|
||||||
|
crc = crc & 0xFFFFFFFF;
|
||||||
|
inNum <<= 1;
|
||||||
|
inNum = inNum & 0xFFFFFFFF;
|
||||||
|
|
||||||
|
if (inNum & 0x100) {
|
||||||
|
++crc;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (crc & 0x10000) {
|
||||||
|
crc ^= 0x1021;
|
||||||
|
crc = crc & 0xFFFFFFFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
} while (!(inNum & 0x10000));
|
||||||
|
|
||||||
|
return crc & 0xffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
function crc16(data, size) {
|
||||||
|
let crc = 0;
|
||||||
|
|
||||||
|
for (let i = 0; i < size; i++) {
|
||||||
|
crc = UpdateCRC16(crc, data[i])
|
||||||
|
}
|
||||||
|
crc = UpdateCRC16(crc, 0);
|
||||||
|
crc = UpdateCRC16(crc, 0);
|
||||||
|
|
||||||
|
return crc & 0xffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function crc16_backup(buf, len) {
|
||||||
|
// let crc = 0x0000;
|
||||||
|
let crc = 0xFFFF;
|
||||||
|
|
||||||
|
for (let pos = 0; pos < len; pos++) {
|
||||||
|
// XOR byte into least sig. byte of crc
|
||||||
|
crc ^= buf[pos];
|
||||||
|
crc = crc & 0xFFFF;
|
||||||
|
for (let i = 8; i != 0; i--) { // Loop over each bit
|
||||||
|
if ((crc & 0x0001) != 0) { // If the LSB is set
|
||||||
|
crc >>= 1; // Shift right and XOR 0xA001
|
||||||
|
crc ^= 0xA001;
|
||||||
|
crc = crc & 0xFFFF;
|
||||||
|
}
|
||||||
|
else {// Else LSB is not set
|
||||||
|
crc >>= 1; // Just shift right
|
||||||
|
crc = crc & 0xFFFF;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return crc;
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
int calcrc(char *ptr, int count)
|
||||||
|
{
|
||||||
|
int crc;
|
||||||
|
char i;
|
||||||
|
crc = 0;
|
||||||
|
while (--count >= 0)
|
||||||
|
{
|
||||||
|
crc = crc ^ (int) *ptr++ << 8;
|
||||||
|
i = 8;
|
||||||
|
do
|
||||||
|
{
|
||||||
|
if (crc & 0x8000)
|
||||||
|
crc = crc << 1 ^ 0x1021;
|
||||||
|
else
|
||||||
|
crc = crc << 1;
|
||||||
|
} while(--i);
|
||||||
|
}
|
||||||
|
return (crc);
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
module.exports = crc16;
|
109
tools/Mini_Program/iap/miniprogram/utils/hardware/ble/packet.js
Normal file
109
tools/Mini_Program/iap/miniprogram/utils/hardware/ble/packet.js
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
|
||||||
|
let crc16 = require('./crc16.js')
|
||||||
|
|
||||||
|
const SOH = 0x01 /* start of 128-byte data packet */
|
||||||
|
const STX = 0x02 /* start of 1024-byte data packet */
|
||||||
|
const EOT = 0x04 /* end of transmission */
|
||||||
|
const ACK = 0x06 /* acknowledge */
|
||||||
|
const NAK = 0x15 /* negative acknowledge */
|
||||||
|
const CA = 0x18 /* two of these in succession aborts transfer */
|
||||||
|
const CRC16 = 0x43 /* 'C' == 0x43, request 16-bit CRC */
|
||||||
|
const NEGATIVE_BYTE = 0xFF
|
||||||
|
|
||||||
|
const ABORT1 = 0x41 /* 'A' == 0x41, abort by user */
|
||||||
|
const ABORT2 = 0x61 /* 'a' == 0x61, abort by user */
|
||||||
|
|
||||||
|
const NAK_TIMEOUT = 10000
|
||||||
|
const DOWNLOAD_TIMEOUT = 1000 /* One second retry delay */
|
||||||
|
const MAX_ERRORS = 10
|
||||||
|
|
||||||
|
const NORMAL_LEN = 128;
|
||||||
|
const LONG_LEN = 1024;
|
||||||
|
const DATA_INDEX = 3;
|
||||||
|
|
||||||
|
function getNormalPacket(id, contentBuf) {
|
||||||
|
let buf = new Uint8Array(NORMAL_LEN + 3 + 2); //NORMAL_LEN
|
||||||
|
let i = 0;
|
||||||
|
buf[i++] = SOH;
|
||||||
|
|
||||||
|
buf[i++] = id;
|
||||||
|
buf[i++] = 0xFF - id;
|
||||||
|
|
||||||
|
if (contentBuf.length > NORMAL_LEN) {
|
||||||
|
throw new Error("Over normal packet size limit");
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let j = 0; j < contentBuf.length; j++) {
|
||||||
|
buf[i++] = contentBuf[j];
|
||||||
|
}
|
||||||
|
|
||||||
|
while (i < NORMAL_LEN + 3) {
|
||||||
|
buf[i++] = 0x1A;
|
||||||
|
}
|
||||||
|
let bufcrc = buf.slice(3, 3 + NORMAL_LEN)
|
||||||
|
let crc = crc16(bufcrc, NORMAL_LEN);
|
||||||
|
|
||||||
|
buf[i++] = (crc >> 8) & 0xFF;
|
||||||
|
buf[i++] = crc & 0xFF;
|
||||||
|
|
||||||
|
// console.log("packet forming End i = ", i);
|
||||||
|
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getLongPacket (id, contentBuf) {
|
||||||
|
let buf = new Uint8Array(LONG_LEN + 3 + 2);
|
||||||
|
let i = 0;
|
||||||
|
buf[i++] = STX;
|
||||||
|
|
||||||
|
buf[i++] = id;
|
||||||
|
buf[i++] = 0xFF - id;
|
||||||
|
|
||||||
|
if (contentBuf.length > LONG_LEN) {
|
||||||
|
throw new Error("Over long packet size limit");
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let j = 0; j < contentBuf.length; j++) {
|
||||||
|
buf[i++] = contentBuf[j];
|
||||||
|
}
|
||||||
|
|
||||||
|
while (i < LONG_LEN + 3) {
|
||||||
|
buf[i++] = 0x1A;
|
||||||
|
}
|
||||||
|
let bufcrc = buf.slice(3, LONG_LEN + 3)
|
||||||
|
let crc = crc16(bufcrc, LONG_LEN);
|
||||||
|
|
||||||
|
buf[i++] = (crc >> 8) & 0xFF;
|
||||||
|
buf[i++] = crc & 0xFF;
|
||||||
|
|
||||||
|
// console.log("packet forming End i = ", i);
|
||||||
|
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
|
function getZeroContent (fileSymbol, fileLen) {
|
||||||
|
|
||||||
|
let buf = new Uint8Array(128);
|
||||||
|
|
||||||
|
// let fileLenBuf = Uint8Array.from(fileLen + '')
|
||||||
|
// let symbolBuf = Uint8Array.from(fileSymbol)
|
||||||
|
let fileLenStr = fileLen.toString()
|
||||||
|
let i = 0, j = 0;
|
||||||
|
for (j = 0; j < fileSymbol.length; j++) {
|
||||||
|
buf[i++] = fileSymbol.charCodeAt(j)
|
||||||
|
}
|
||||||
|
buf[i++] = 0;
|
||||||
|
for (j = 0; j < fileLenStr.length; j++) {
|
||||||
|
buf[i++] = fileLenStr.charCodeAt(j)
|
||||||
|
}
|
||||||
|
// buf[i++] = 32;
|
||||||
|
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
let packet = {
|
||||||
|
getZeroContent: getZeroContent,
|
||||||
|
getNormalPacket: getNormalPacket,
|
||||||
|
getLongPacket: getLongPacket
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = packet;
|
97
tools/Mini_Program/iap/miniprogram/utils/util.js
Normal file
97
tools/Mini_Program/iap/miniprogram/utils/util.js
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
const formatTime = function(date) {
|
||||||
|
if (!date) {
|
||||||
|
date = new Date()
|
||||||
|
}
|
||||||
|
const year = date.getFullYear()
|
||||||
|
const month = date.getMonth() + 1
|
||||||
|
const day = date.getDate()
|
||||||
|
const hour = date.getHours()
|
||||||
|
const minute = date.getMinutes()
|
||||||
|
const second = date.getSeconds()
|
||||||
|
|
||||||
|
return [year, month, day].map(formatNumber).join('-') + ' ' + [hour, minute, second].map(formatNumber).join(':')
|
||||||
|
}
|
||||||
|
|
||||||
|
const formatNumber = n => {
|
||||||
|
n = n.toString()
|
||||||
|
return n[1] ? n : '0' + n
|
||||||
|
}
|
||||||
|
|
||||||
|
// await util.delayMs(1000)
|
||||||
|
const delayMs = (ms) => {
|
||||||
|
return new Promise(resolve => {
|
||||||
|
setTimeout(resolve, ms);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// ArrayBuffer转16进制字符串
|
||||||
|
// ArrayBuffer => hex string
|
||||||
|
function ab2strHex(ab) {
|
||||||
|
let hexArr = Array.prototype.map.call(
|
||||||
|
new Uint8Array(ab),
|
||||||
|
function(bit) {
|
||||||
|
return ('00' + bit.toString(16)).slice(-2)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
return hexArr.join('');
|
||||||
|
}
|
||||||
|
|
||||||
|
// string => ArrayBuffer Uint8
|
||||||
|
function str2abUint8(str) {
|
||||||
|
var buf = new ArrayBuffer(str.length); // 2 bytes for each char
|
||||||
|
var bufView = new Uint8Array(buf);
|
||||||
|
for (var i = 0, strLen = str.length; i < strLen; i++) {
|
||||||
|
bufView[i] = str.charCodeAt(i);
|
||||||
|
}
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
// string => ArrayBuffer Uint16
|
||||||
|
function str2abUint16(str) {
|
||||||
|
var buf = new ArrayBuffer(str.length * 2); // 2 bytes for each char
|
||||||
|
var bufView = new Uint16Array(buf);
|
||||||
|
for (var i = 0, strLen = str.length; i < strLen; i++) {
|
||||||
|
bufView[i] = str.charCodeAt(i);
|
||||||
|
}
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
function ArrayBuffer2str(buf) {
|
||||||
|
return String.fromCharCode.apply(null, new Uint8Array(buf));
|
||||||
|
}
|
||||||
|
|
||||||
|
function Uint8Array2str(arr) {
|
||||||
|
return String.fromCharCode.apply(null, arr);
|
||||||
|
}
|
||||||
|
|
||||||
|
// let retryPromise = util.autoRetry(promise, 1);
|
||||||
|
// refer to: https://blog.csdn.net/github_38589282/article/details/77414358
|
||||||
|
function autoRetry(func, retryMax) {
|
||||||
|
let retryNum = retryMax;
|
||||||
|
return async function funcR() {
|
||||||
|
let params = arguments
|
||||||
|
while (retryNum--) {
|
||||||
|
try {
|
||||||
|
await func(...params)
|
||||||
|
} catch (e) {
|
||||||
|
if (retryNum > 0) {
|
||||||
|
console.error(`retry ${retryMax - retryNum} time. error:`, e);
|
||||||
|
continue
|
||||||
|
} else {
|
||||||
|
throw e
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
// autoRetry: autoRetry,
|
||||||
|
formatTime: formatTime,
|
||||||
|
delayMs: delayMs,
|
||||||
|
ab2strHex: ab2strHex,
|
||||||
|
str2abUint8: str2abUint8,
|
||||||
|
str2abUint16: str2abUint16,
|
||||||
|
ArrayBuffer2str: ArrayBuffer2str,
|
||||||
|
Uint8Array2str: Uint8Array2str,
|
||||||
|
}
|
73
tools/Mini_Program/iap/project.config.json
Normal file
73
tools/Mini_Program/iap/project.config.json
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
{
|
||||||
|
"miniprogramRoot": "miniprogram/",
|
||||||
|
"cloudfunctionRoot": "cloudfunctions/",
|
||||||
|
"description": "项目配置文件",
|
||||||
|
"packOptions": {
|
||||||
|
"ignore": []
|
||||||
|
},
|
||||||
|
"setting": {
|
||||||
|
"urlCheck": true,
|
||||||
|
"es6": true,
|
||||||
|
"postcss": true,
|
||||||
|
"minified": true,
|
||||||
|
"newFeature": true,
|
||||||
|
"coverView": true,
|
||||||
|
"autoAudits": false,
|
||||||
|
"showShadowRootInWxmlPanel": true,
|
||||||
|
"scopeDataCheck": false,
|
||||||
|
"checkInvalidKey": true,
|
||||||
|
"checkSiteMap": true,
|
||||||
|
"uploadWithSourceMap": true,
|
||||||
|
"babelSetting": {
|
||||||
|
"ignore": [],
|
||||||
|
"disablePlugins": [],
|
||||||
|
"outputPath": ""
|
||||||
|
},
|
||||||
|
"enhance": true
|
||||||
|
},
|
||||||
|
"compileType": "miniprogram",
|
||||||
|
"libVersion": "2.10.2",
|
||||||
|
"appid": "wx394efa031612f9b5",
|
||||||
|
"projectname": "iap",
|
||||||
|
"debugOptions": {
|
||||||
|
"hidedInDevtools": []
|
||||||
|
},
|
||||||
|
"isGameTourist": false,
|
||||||
|
"simulatorType": "wechat",
|
||||||
|
"simulatorPluginLibVersion": {},
|
||||||
|
"cloudfunctionTemplateRoot": "cloudfunctionTemplate",
|
||||||
|
"condition": {
|
||||||
|
"search": {
|
||||||
|
"current": -1,
|
||||||
|
"list": []
|
||||||
|
},
|
||||||
|
"conversation": {
|
||||||
|
"current": -1,
|
||||||
|
"list": []
|
||||||
|
},
|
||||||
|
"plugin": {
|
||||||
|
"current": -1,
|
||||||
|
"list": []
|
||||||
|
},
|
||||||
|
"game": {
|
||||||
|
"currentL": -1,
|
||||||
|
"list": []
|
||||||
|
},
|
||||||
|
"gamePlugin": {
|
||||||
|
"current": -1,
|
||||||
|
"list": []
|
||||||
|
},
|
||||||
|
"miniprogram": {
|
||||||
|
"current": -1,
|
||||||
|
"list": [
|
||||||
|
{
|
||||||
|
"id": 0,
|
||||||
|
"name": "ble-device",
|
||||||
|
"pathName": "pages/hardware/ble/device/index",
|
||||||
|
"query": "",
|
||||||
|
"scene": null
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user