diff --git a/tools/Mini_Program/iap/README.md b/tools/Mini_Program/iap/README.md
new file mode 100644
index 000000000..dd8d33788
--- /dev/null
+++ b/tools/Mini_Program/iap/README.md
@@ -0,0 +1,2 @@
+## 蓝牙升级微信小程序源码
+
diff --git a/tools/Mini_Program/iap/miniprogram/app.js b/tools/Mini_Program/iap/miniprogram/app.js
new file mode 100644
index 000000000..9c5d25e2b
--- /dev/null
+++ b/tools/Mini_Program/iap/miniprogram/app.js
@@ -0,0 +1,12 @@
+//app.js
+App({
+ globalData: {
+ },
+ onLaunch: function () {
+ this.globalData.SystemInfo = wx.getSystemInfoSync()
+ //console.log(this.globalData.SystemInfo)
+ wx.setKeepScreenOn({
+ keepScreenOn: true
+ })
+ },
+})
\ No newline at end of file
diff --git a/tools/Mini_Program/iap/miniprogram/app.json b/tools/Mini_Program/iap/miniprogram/app.json
new file mode 100644
index 000000000..6d45376b0
--- /dev/null
+++ b/tools/Mini_Program/iap/miniprogram/app.json
@@ -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"
+}
\ No newline at end of file
diff --git a/tools/Mini_Program/iap/miniprogram/app.wxss b/tools/Mini_Program/iap/miniprogram/app.wxss
new file mode 100644
index 000000000..7dfbc1bc6
--- /dev/null
+++ b/tools/Mini_Program/iap/miniprogram/app.wxss
@@ -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;
+}
\ No newline at end of file
diff --git a/tools/Mini_Program/iap/miniprogram/images/aithinker-mini.png b/tools/Mini_Program/iap/miniprogram/images/aithinker-mini.png
new file mode 100644
index 000000000..87743378c
Binary files /dev/null and b/tools/Mini_Program/iap/miniprogram/images/aithinker-mini.png differ
diff --git a/tools/Mini_Program/iap/miniprogram/images/bluetooth.png b/tools/Mini_Program/iap/miniprogram/images/bluetooth.png
new file mode 100644
index 000000000..fdfe30c03
Binary files /dev/null and b/tools/Mini_Program/iap/miniprogram/images/bluetooth.png differ
diff --git a/tools/Mini_Program/iap/miniprogram/pages/hardware/ble/device/index.js b/tools/Mini_Program/iap/miniprogram/pages/hardware/ble/device/index.js
new file mode 100644
index 000000000..1af9f776a
--- /dev/null
+++ b/tools/Mini_Program/iap/miniprogram/pages/hardware/ble/device/index.js
@@ -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,
+ })
+ }
+ },
+})
\ No newline at end of file
diff --git a/tools/Mini_Program/iap/miniprogram/pages/hardware/ble/device/index.json b/tools/Mini_Program/iap/miniprogram/pages/hardware/ble/device/index.json
new file mode 100644
index 000000000..8835af069
--- /dev/null
+++ b/tools/Mini_Program/iap/miniprogram/pages/hardware/ble/device/index.json
@@ -0,0 +1,3 @@
+{
+ "usingComponents": {}
+}
\ No newline at end of file
diff --git a/tools/Mini_Program/iap/miniprogram/pages/hardware/ble/device/index.wxml b/tools/Mini_Program/iap/miniprogram/pages/hardware/ble/device/index.wxml
new file mode 100644
index 000000000..fb70629ef
--- /dev/null
+++ b/tools/Mini_Program/iap/miniprogram/pages/hardware/ble/device/index.wxml
@@ -0,0 +1,94 @@
+
+
+
+
+ 已连接
+
+
+ 已断开
+
+
+ 未连接
+
+
+
+
+
+ 设备信息
+
+
+
+ 设备名称
+ {{device.name}}
+
+
+ 设备ID
+ {{device.deviceId}}
+
+
+ 连接状态
+
+
+
+
+
+
+
+
+
+ 串口输出
+
+
+
+
+
+
+
+
+ 固件升级
+
+
+
+ 菜单选项
+
+
+
+
+
+
+
+
+
+ 固件升级
+
+
+
+
+
+
+
+
+ 文件名(大小)
+ {{file_name}} ({{file_size}})
+
+
+
+
+
+
+
+ begin@{{timestamp_start}}
+ finish@{{timestamp_end}}
+ rate@{{rate}}(B/s)
+
+
+
+
+
+
\ No newline at end of file
diff --git a/tools/Mini_Program/iap/miniprogram/pages/hardware/ble/device/index.wxss b/tools/Mini_Program/iap/miniprogram/pages/hardware/ble/device/index.wxss
new file mode 100644
index 000000000..aa7f1f4c0
--- /dev/null
+++ b/tools/Mini_Program/iap/miniprogram/pages/hardware/ble/device/index.wxss
@@ -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
+}
\ No newline at end of file
diff --git a/tools/Mini_Program/iap/miniprogram/pages/hardware/ble/index/index.js b/tools/Mini_Program/iap/miniprogram/pages/hardware/ble/index/index.js
new file mode 100644
index 000000000..e705f58a6
--- /dev/null
+++ b/tools/Mini_Program/iap/miniprogram/pages/hardware/ble/index/index.js
@@ -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"
+ })
+ }
+ },
+})
\ No newline at end of file
diff --git a/tools/Mini_Program/iap/miniprogram/pages/hardware/ble/index/index.json b/tools/Mini_Program/iap/miniprogram/pages/hardware/ble/index/index.json
new file mode 100644
index 000000000..8835af069
--- /dev/null
+++ b/tools/Mini_Program/iap/miniprogram/pages/hardware/ble/index/index.json
@@ -0,0 +1,3 @@
+{
+ "usingComponents": {}
+}
\ No newline at end of file
diff --git a/tools/Mini_Program/iap/miniprogram/pages/hardware/ble/index/index.wxml b/tools/Mini_Program/iap/miniprogram/pages/hardware/ble/index/index.wxml
new file mode 100644
index 000000000..ca2584fe9
--- /dev/null
+++ b/tools/Mini_Program/iap/miniprogram/pages/hardware/ble/index/index.wxml
@@ -0,0 +1,15 @@
+
+
+
+
+
+ 设备名称: {{item.name}}
+ 设备ID: {{item.deviceId}}
+ 信号强度RSSI: {{item.RSSI}}
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/tools/Mini_Program/iap/miniprogram/pages/hardware/ble/index/index.wxss b/tools/Mini_Program/iap/miniprogram/pages/hardware/ble/index/index.wxss
new file mode 100644
index 000000000..b2547423d
--- /dev/null
+++ b/tools/Mini_Program/iap/miniprogram/pages/hardware/ble/index/index.wxss
@@ -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;
+}
\ No newline at end of file
diff --git a/tools/Mini_Program/iap/miniprogram/sitemap.json b/tools/Mini_Program/iap/miniprogram/sitemap.json
new file mode 100644
index 000000000..bb97574e0
--- /dev/null
+++ b/tools/Mini_Program/iap/miniprogram/sitemap.json
@@ -0,0 +1,7 @@
+{
+ "desc": "关于本文件的更多信息,请参考文档 https://developers.weixin.qq.com/miniprogram/dev/framework/sitemap.html",
+ "rules": [{
+ "action": "allow",
+ "page": "*"
+ }]
+}
\ No newline at end of file
diff --git a/tools/Mini_Program/iap/miniprogram/utils/hardware/ble/ble-api.js b/tools/Mini_Program/iap/miniprogram/utils/hardware/ble/ble-api.js
new file mode 100644
index 000000000..49f57d3e8
--- /dev/null
+++ b/tools/Mini_Program/iap/miniprogram/utils/hardware/ble/ble-api.js
@@ -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,
+}
\ No newline at end of file
diff --git a/tools/Mini_Program/iap/miniprogram/utils/hardware/ble/crc16.js b/tools/Mini_Program/iap/miniprogram/utils/hardware/ble/crc16.js
new file mode 100644
index 000000000..c201245e9
--- /dev/null
+++ b/tools/Mini_Program/iap/miniprogram/utils/hardware/ble/crc16.js
@@ -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;
\ No newline at end of file
diff --git a/tools/Mini_Program/iap/miniprogram/utils/hardware/ble/packet.js b/tools/Mini_Program/iap/miniprogram/utils/hardware/ble/packet.js
new file mode 100644
index 000000000..9002302f2
--- /dev/null
+++ b/tools/Mini_Program/iap/miniprogram/utils/hardware/ble/packet.js
@@ -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;
\ No newline at end of file
diff --git a/tools/Mini_Program/iap/miniprogram/utils/util.js b/tools/Mini_Program/iap/miniprogram/utils/util.js
new file mode 100644
index 000000000..322851b93
--- /dev/null
+++ b/tools/Mini_Program/iap/miniprogram/utils/util.js
@@ -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,
+}
\ No newline at end of file
diff --git a/tools/Mini_Program/iap/project.config.json b/tools/Mini_Program/iap/project.config.json
new file mode 100644
index 000000000..fa9dac5fa
--- /dev/null
+++ b/tools/Mini_Program/iap/project.config.json
@@ -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
+ }
+ ]
+ }
+ }
+}
\ No newline at end of file