miniprogram: add iap mp for ble OTA

This commit is contained in:
royye62
2020-04-14 16:50:37 +08:00
parent e5c71448c7
commit 3500ccd27c
20 changed files with 1792 additions and 0 deletions

View 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,
}

View File

@@ -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;

View 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;

View 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,
}