1843 lines
415 KiB
JavaScript
1843 lines
415 KiB
JavaScript
|
|
/******************************************************************************
|
|||
|
|
Copyright (c) Microsoft Corporation.
|
|||
|
|
|
|||
|
|
Permission to use, copy, modify, and/or distribute this software for any
|
|||
|
|
purpose with or without fee is hereby granted.
|
|||
|
|
|
|||
|
|
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
|||
|
|
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
|||
|
|
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
|||
|
|
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
|||
|
|
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
|||
|
|
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
|||
|
|
PERFORMANCE OF THIS SOFTWARE.
|
|||
|
|
***************************************************************************** */
|
|||
|
|
/* global Reflect, Promise, SuppressedError, Symbol, Iterator */
|
|||
|
|
|
|||
|
|
|
|||
|
|
function __awaiter(thisArg, _arguments, P, generator) {
|
|||
|
|
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|||
|
|
return new (P || (P = Promise))(function (resolve, reject) {
|
|||
|
|
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|||
|
|
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|||
|
|
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|||
|
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
|
|||
|
|
var e = new Error(message);
|
|||
|
|
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
function t(t,e,i,s){return new(i||(i=Promise))(function(n,r){function a(t){try{h(s.next(t));}catch(t){r(t);}}function o(t){try{h(s.throw(t));}catch(t){r(t);}}function h(t){var e;t.done?n(t.value):(e=t.value,e instanceof i?e:new i(function(t){t(e);})).then(a,o);}h((s=s.apply(t,[])).next());})}"function"==typeof SuppressedError&&SuppressedError;class e{static formatDate(t,e){let i=t||"yyyy-MM-dd HH:mm:ss.SSS";const s={"y+":(e=e||new Date).getFullYear(),"M+":e.getMonth()+1,"d+":e.getDate(),"h+":e.getHours(),"H+":e.getHours(),"m+":e.getMinutes(),"s+":e.getSeconds(),"q+":Math.floor((e.getMonth()+3)/3),"S+":e.getMilliseconds()};let n;for(const t in s)if(n=new RegExp(`(${t})`).exec(i)){const e=`${s[t]}`,r=`00${e}`,a=n[1].length>r.length?0:r.length-n[1].length;i=i.replace(n[1],1===n[1].length?e:r.substring(a));}return i}static randomUUID(t){if("object"==typeof crypto){if("function"==typeof crypto.randomUUID)return t?crypto.randomUUID():crypto.randomUUID().toUpperCase();if("function"==typeof crypto.getRandomValues&&"function"==typeof Uint8Array){const e="10000000-1000-4000-8000-100000000000".replace(/[018]/g,t=>{const e=Number(t);return (e^crypto.getRandomValues(new Uint8Array(1))[0]&15>>e/4).toString(16)});return t?e.toUpperCase():e}}let e=(new Date).getTime(),i="undefined"!=typeof performance&&performance.now&&1e3*performance.now()||0;const s="xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,t=>{let s=16*Math.random();return e>0?(s=(e+s)%16|0,e=Math.floor(e/16)):(s=(i+s)%16|0,i=Math.floor(i/16)),("x"===t?s:3&s|8).toString(16)});return t?s.toUpperCase():s}static getRandomValues(t,e){const i=e&&e>0?e:256,s=[],n=t&&t>0?t:1;let r=(new Date).getTime(),a="undefined"!=typeof performance&&performance.now&&1e3*performance.now()||0;for(let t=0;t<n;t++){let e=Math.random()*i;r>0?(e=(r+e)%i|0,r=Math.floor(r/i)):(e=(a+e)%i|0,a=Math.floor(a/i)),s[t]=e;}return s}static getRandomStr(t){const e=t&&t>0?t:8;if("object"==typeof crypto&&"function"==typeof crypto.getRandomValues&&"function"==typeof Uint8Array){const t=crypto.getRandomValues(new Uint8Array(e));return Array.from(t).map(t=>(15&t).toString(16)).join("")}return this.getRandomValues(e,16).map(t=>(15&t).toString(16)).join("")}static createRect(t,e,i,s){return {x:t||0,y:e||0,width:i,height:s}}static parseRect(t){if(!t)return;t=t.trim();const e=/^\[?\(?\s*([0-9.+-]+)\s*,\s*([0-9.+-]+)\s*,\s*([0-9.+-]+)\s*,\s*([0-9.+-]+)\s*\)?]?$/.exec(t);return e?{x:parseFloat(e[1]),y:parseFloat(e[2]),width:parseFloat(e[3]),height:parseFloat(e[4])}:void 0}static isInnerRect(t,e){return t.x>=e.x&&t.x+t.width<=e.x+e.width&&t.y>=e.y&&t.y+t.height<=e.y+e.height}static hasIntersection(t,e){return t.x<e.x+e.width&&e.x<t.x+t.width&&t.y<e.y+e.height&&e.y<t.y+t.height}static getUnionRect(t,e){const i=Math.min(t.x,e.x),s=Math.min(t.y,e.y);return {x:i,y:s,width:Math.max(t.x+t.width,e.x+e.width)-i,height:Math.max(t.y+t.height,e.y+e.height)-s}}static rotate90(t){const e=t.x+.5*t.width,i=t.y+.5*t.height,s=t.width,n=t.height;return t.width>0&&t.height>0&&(t.width=n,t.height=s,t.x=e-.5*n,t.y=i-.5*s),t}}var i,s,n,r,a,o,h,c,d,u;!function(t){t[t.NONE=0]="NONE",t[t.ERROR=1]="ERROR",t[t.WARN=2]="WARN",t[t.INFO=3]="INFO",t[t.LOG=4]="LOG";}(i||(i={}));class l{static innerLog(t,...e){this.output?this.output.log(t,...e):console.log(t,...e);}static innerWarn(t,...e){this.output?this.output.warn(t,...e):console.warn(t,...e);}static innerError(t,...e){this.output?this.output.error(t,...e):console.error(t,...e);}static setLevel(t){t!==this.logLevel&&(this.logRecords.push(this.logLevel),this.logLevel=t,this.logRecords.length>this.RecordMaxSize&&this.logRecords.shift(),this.info(`<<================ setLogLevel(${t}) ================>>`));}static setLogOutput(t){this.output=t;}static restorePrev(){const t=this.logLevel;if(this.logRecords.length>0){const e=this.logRecords.shift();return this.logLevel=void 0===e?i.WARN:e,t}return -1}static logWithLevelFunc(t,i,s,...n){const r=e.formatDate("HH:mm:ss.SSS");"string"!=typeof s?1===n.length&&"string"==typeof n[0]?(t.call(l,`${i}[${r}]: ${n[0]||""}`),t.call(l,s)):t.call(l,s,...n):t.call(l,`
|
|||
|
|
|
|||
|
|
class UniCanvas {
|
|||
|
|
get width() {
|
|||
|
|
return this._width;
|
|||
|
|
}
|
|||
|
|
set width(v) {
|
|||
|
|
this._width = v;
|
|||
|
|
}
|
|||
|
|
get height() {
|
|||
|
|
return this._height;
|
|||
|
|
}
|
|||
|
|
set height(v) {
|
|||
|
|
this._height = v;
|
|||
|
|
}
|
|||
|
|
get canvasId() {
|
|||
|
|
return this.mContext.canvasId;
|
|||
|
|
}
|
|||
|
|
constructor(context) {
|
|||
|
|
this._width = 0;
|
|||
|
|
this._height = 0;
|
|||
|
|
this.mContext = context;
|
|||
|
|
}
|
|||
|
|
getContext(options) {
|
|||
|
|
if (!this.mCanvasContext) {
|
|||
|
|
this.mCanvasContext = uni.createCanvasContext(this.mContext.canvasId || "", this.mContext.componentInstance);
|
|||
|
|
}
|
|||
|
|
return this.mCanvasContext;
|
|||
|
|
}
|
|||
|
|
toDataURL(type, quality) {
|
|||
|
|
return this.mDataUrl || "";
|
|||
|
|
}
|
|||
|
|
getImageData() {
|
|||
|
|
return new Promise((resolve) => {
|
|||
|
|
const context = this.mCanvasContext;
|
|||
|
|
if (!context) {
|
|||
|
|
resolve(undefined);
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
const funcType = typeof context.getImageData;
|
|||
|
|
l.info(`【UniCanvas.Request】 getImageData: typeof context.getImageData = ${funcType}`);
|
|||
|
|
if (this.mContext.isAlipay) {
|
|||
|
|
// 支付宝小程序旧版本,只能异步获取ImageData;
|
|||
|
|
// 备注:这个地方会报错,报 toJSON 错误,但是不影响打印,后期需要升级 context 获取方式;
|
|||
|
|
if (this.mContext.canvas) {
|
|||
|
|
// 新版本不需要特殊处理
|
|||
|
|
resolve(undefined);
|
|||
|
|
}
|
|||
|
|
else {
|
|||
|
|
// 旧版本需要异步处理;
|
|||
|
|
if (funcType === "function") {
|
|||
|
|
const timer = setTimeout(() => {
|
|||
|
|
l.warn(`【UniCanvas.Response.timeout】 getImageData 请求超时!`);
|
|||
|
|
resolve(undefined);
|
|||
|
|
}, 5000);
|
|||
|
|
const resolveData = (data) => {
|
|||
|
|
clearTimeout(timer);
|
|||
|
|
resolve(data);
|
|||
|
|
};
|
|||
|
|
const res = context.getImageData({
|
|||
|
|
x: 0,
|
|||
|
|
y: 0,
|
|||
|
|
width: this.width,
|
|||
|
|
height: this.height,
|
|||
|
|
success: (res) => {
|
|||
|
|
l.info(`【UniCanvas.Response.success】 getImageData:`);
|
|||
|
|
clearTimeout(timer);
|
|||
|
|
resolveData(res);
|
|||
|
|
},
|
|||
|
|
fail: (resp) => {
|
|||
|
|
l.warn(resp, `【UniCanvas.Response.fail】 getImageData:`, resp);
|
|||
|
|
clearTimeout(timer);
|
|||
|
|
resolveData(undefined);
|
|||
|
|
},
|
|||
|
|
});
|
|||
|
|
// 钉钉小程序返回值是 Promise,支付宝小程序返回值为空。
|
|||
|
|
if (Le.isPromise(res)) {
|
|||
|
|
Promise.resolve(res)
|
|||
|
|
.then((_res) => {
|
|||
|
|
// logger.info(`【UniCanvas.Response.then】 getImageData:`, _res);
|
|||
|
|
})
|
|||
|
|
.catch((err) => {
|
|||
|
|
l.info(`【UniCanvas.Response.catch】 getImageData:`, err);
|
|||
|
|
resolveData(undefined);
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
else {
|
|||
|
|
l.warn(`【UniCanvas.Response】 getImageData: 不支持的方法:`);
|
|||
|
|
resolve(undefined);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
else if (typeof uni.canvasGetImageData === "function") {
|
|||
|
|
// APP、微信小程序等,需要通过 canvasGetImageData 来获取imageData 对象;
|
|||
|
|
l.info(`#### 【Request】 uni.canvasGetImageData:`);
|
|||
|
|
uni.canvasGetImageData({
|
|||
|
|
x: 0,
|
|||
|
|
y: 0,
|
|||
|
|
width: this.width,
|
|||
|
|
height: this.height,
|
|||
|
|
canvasId: this.mContext.canvasId || "",
|
|||
|
|
success: (res) => {
|
|||
|
|
l.info(`#### 【Response.success】 uni.canvasGetImageData:`);
|
|||
|
|
resolve(res);
|
|||
|
|
},
|
|||
|
|
fail: (res) => {
|
|||
|
|
l.error(`#### 【Response.fail】uni.canvasGetImageData:`);
|
|||
|
|
l.error(JSON.stringify(res));
|
|||
|
|
resolve(undefined);
|
|||
|
|
},
|
|||
|
|
}, this.mContext.componentInstance);
|
|||
|
|
}
|
|||
|
|
else {
|
|||
|
|
resolve(undefined);
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
static getTempFilePath(options, callback) {
|
|||
|
|
l.info(`#### 【Request】uni.canvasToTempFilePath:`);
|
|||
|
|
let timer = setTimeout(() => {
|
|||
|
|
l.warn(`#### 【Response.timeout】uni.canvasToTempFilePath:`);
|
|||
|
|
callback("");
|
|||
|
|
}, 3000);
|
|||
|
|
//
|
|||
|
|
uni.canvasToTempFilePath({
|
|||
|
|
// 保存canvas为图片
|
|||
|
|
canvasId: options.canvasId || "",
|
|||
|
|
canvas: options.canvas,
|
|||
|
|
quality: 1,
|
|||
|
|
success: (res) => {
|
|||
|
|
l.info(res, `#### 【Response.success】uni.canvasToTempFilePath:`);
|
|||
|
|
// 备注:钉钉小程序需要通过 filePath来获取
|
|||
|
|
// uni.setStorageSync('filePath',res.tempFilePath) //保存临时文件路径到缓存
|
|||
|
|
clearTimeout(timer);
|
|||
|
|
callback(res.tempFilePath || res.filePath);
|
|||
|
|
},
|
|||
|
|
fail: (res) => {
|
|||
|
|
l.warn(res, `#### 【Response.fail】uni.canvasToTempFilePath:`);
|
|||
|
|
clearTimeout(timer);
|
|||
|
|
callback("");
|
|||
|
|
},
|
|||
|
|
}, options.componentInstance);
|
|||
|
|
}
|
|||
|
|
start(options) {
|
|||
|
|
this.mDataUrl = "";
|
|||
|
|
}
|
|||
|
|
draw(context, printMode, reserve) {
|
|||
|
|
l.log(`---- UniCanvas.draw: typeof context.draw = ${typeof context.draw}`);
|
|||
|
|
return new Promise((resolve) => {
|
|||
|
|
if (!context) {
|
|||
|
|
resolve("");
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
const getTempPathAction = () => {
|
|||
|
|
const ddContext = context;
|
|||
|
|
const funcType = this.mContext.isAlipay ? typeof ddContext.toTempFilePath : undefined;
|
|||
|
|
console.warn(`---- typeof context.toTempFilePath = ${funcType}`);
|
|||
|
|
// 备注:支付宝小程序也支持 toTempFilePath,但是返回的图片是空白。
|
|||
|
|
if (this.mContext.isDingTalk && funcType === "function") {
|
|||
|
|
const timer = setTimeout(() => {
|
|||
|
|
l.log(`【UniCanvas.Response.timeout】 dd.Context.toTempFilePath:`);
|
|||
|
|
resolve("");
|
|||
|
|
}, 3000);
|
|||
|
|
const resolveUrl = (url) => {
|
|||
|
|
clearTimeout(timer);
|
|||
|
|
resolve(url);
|
|||
|
|
};
|
|||
|
|
//
|
|||
|
|
l.log(`【UniCanvas.Request】 dd.Context.toTempFilePath:`);
|
|||
|
|
const res = ddContext.toTempFilePath({
|
|||
|
|
x: 0,
|
|||
|
|
y: 0,
|
|||
|
|
width: this.width,
|
|||
|
|
height: this.height,
|
|||
|
|
success: (res) => {
|
|||
|
|
const url = res.tempFilePath || res.url || "";
|
|||
|
|
this.mDataUrl = url;
|
|||
|
|
l.log(`【UniCanvas.Response.success】 context.toTempFilePath:`, res);
|
|||
|
|
resolveUrl(url);
|
|||
|
|
},
|
|||
|
|
fail: (err) => {
|
|||
|
|
l.log(`【UniCanvas.Response.fail】 context.toTempFilePath:`, err);
|
|||
|
|
console.warn(`---- is fail to getTempFilePath: `, err);
|
|||
|
|
resolveUrl("");
|
|||
|
|
},
|
|||
|
|
});
|
|||
|
|
// 钉钉小程序返回值是 Promise,支付宝小程序返回值为空。
|
|||
|
|
if (Le.isPromise(res)) {
|
|||
|
|
Promise.resolve(res)
|
|||
|
|
.then((res) => {
|
|||
|
|
l.log(`【UniCanvas.Response.then】 context.toTempFilePath:`, res);
|
|||
|
|
})
|
|||
|
|
.catch((err) => {
|
|||
|
|
l.log(`【UniCanvas.Response.catch】 context.toTempFilePath:`, err);
|
|||
|
|
resolveUrl("");
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
else {
|
|||
|
|
UniCanvas.getTempFilePath(this.mContext, (tempPath) => {
|
|||
|
|
this.mDataUrl = tempPath;
|
|||
|
|
resolve(tempPath);
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
if (this.mContext.isAlipay) {
|
|||
|
|
// 支付宝小程序 draw函数没有回调。
|
|||
|
|
if (typeof context.draw === "function") {
|
|||
|
|
context.draw(reserve);
|
|||
|
|
}
|
|||
|
|
//
|
|||
|
|
if (printMode) {
|
|||
|
|
resolve("");
|
|||
|
|
}
|
|||
|
|
else {
|
|||
|
|
setTimeout(() => {
|
|||
|
|
getTempPathAction();
|
|||
|
|
}, this.mContext.drawTimeout || 100);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
else if (typeof context.draw === "function") {
|
|||
|
|
let hasDraw = false;
|
|||
|
|
context.draw(reserve, () => {
|
|||
|
|
hasDraw = true;
|
|||
|
|
if (!printMode) {
|
|||
|
|
// 微信小程序中为了避免生成预览图片黑块的问题
|
|||
|
|
setTimeout(getTempPathAction, this.mContext.drawTimeout || 100);
|
|||
|
|
}
|
|||
|
|
else {
|
|||
|
|
resolve("");
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
setTimeout(() => {
|
|||
|
|
if (!hasDraw) {
|
|||
|
|
l.warn(`---- Canvas.draw 函数长时间未响应,请检查目标 Canvas 是否已经销毁。`);
|
|||
|
|
resolve("");
|
|||
|
|
}
|
|||
|
|
}, 3000);
|
|||
|
|
}
|
|||
|
|
else if (this.mContext.canvas && !printMode) {
|
|||
|
|
// 为了避免出现黑块,暂时等待1秒,具体待测试。
|
|||
|
|
setTimeout(() => {
|
|||
|
|
getTempPathAction();
|
|||
|
|
}, 10);
|
|||
|
|
}
|
|||
|
|
else {
|
|||
|
|
resolve("");
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
saveBase64Image(dataUrl, callback) {
|
|||
|
|
const groups = /^data:(\S+)\/(\S+);base64,.*/.exec(dataUrl || "");
|
|||
|
|
if (groups && plus && plus.nativeObj.Bitmap) {
|
|||
|
|
if (groups[2] === "svg+xml") {
|
|||
|
|
callback && callback(dataUrl);
|
|||
|
|
}
|
|||
|
|
else {
|
|||
|
|
const fileName = `border.${groups[2]}`;
|
|||
|
|
const bitmap = new plus.nativeObj.Bitmap(`bitmap${Date.now()}`);
|
|||
|
|
try {
|
|||
|
|
bitmap.loadBase64Data(dataUrl, () => {
|
|||
|
|
const filePath = `_doc/temp/${fileName}`;
|
|||
|
|
bitmap.save(filePath, { overwrite: true }, (res) => {
|
|||
|
|
bitmap.clear();
|
|||
|
|
callback && callback(res);
|
|||
|
|
}, (res) => {
|
|||
|
|
bitmap.clear();
|
|||
|
|
l.warn(`---- 图片保存失败!`);
|
|||
|
|
l.warn(res);
|
|||
|
|
callback && callback(dataUrl);
|
|||
|
|
});
|
|||
|
|
}, (res) => {
|
|||
|
|
bitmap.clear();
|
|||
|
|
l.warn(`---- base64 字符串加载失败:`);
|
|||
|
|
l.warn(res);
|
|||
|
|
callback && callback(dataUrl);
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
catch (error) {
|
|||
|
|
l.warn(`---- base64 字符串load 异常!`);
|
|||
|
|
l.warn(error);
|
|||
|
|
bitmap.clear();
|
|||
|
|
callback && callback(dataUrl);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
else {
|
|||
|
|
callback && callback(dataUrl);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
loadImage(options) {
|
|||
|
|
const opts = typeof options === "string" ? { src: options } : options;
|
|||
|
|
l.log(`UniCanvas.loadImage: ${opts.src.length > 100 ? opts.src.substring(0, 100) : opts.src}`);
|
|||
|
|
return new Promise((resolve) => {
|
|||
|
|
if (opts.src.startsWith("http")) {
|
|||
|
|
uni.getImageInfo({
|
|||
|
|
src: opts.src,
|
|||
|
|
success: (res) => {
|
|||
|
|
l.log(`res, 【Response.success】uni.getImageInfo:`);
|
|||
|
|
resolve({
|
|||
|
|
width: res.width,
|
|||
|
|
height: res.height,
|
|||
|
|
dzSrc: res.path,
|
|||
|
|
});
|
|||
|
|
},
|
|||
|
|
fail: (res) => {
|
|||
|
|
l.warn(res, `【Response.fail】uni.getImageInfo:`);
|
|||
|
|
resolve("");
|
|||
|
|
},
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
else if (opts.src.startsWith("data:")) {
|
|||
|
|
if (opts.withSize) {
|
|||
|
|
this.saveBase64Image(opts.src, (result) => {
|
|||
|
|
if (result && typeof result !== "string" && result.width && result.height) {
|
|||
|
|
resolve({
|
|||
|
|
width: result.width,
|
|||
|
|
height: result.height,
|
|||
|
|
dzSrc: result.target,
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
else {
|
|||
|
|
resolve(opts.src);
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
else {
|
|||
|
|
resolve(opts.src);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
else {
|
|||
|
|
resolve(opts.src);
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
class UniContext extends xe {
|
|||
|
|
static createInstance(options, prevContext) {
|
|||
|
|
const opts = options || {};
|
|||
|
|
if (prevContext) {
|
|||
|
|
const prevCanvas = prevContext.CanvasElement;
|
|||
|
|
if (options.canvas && options.canvas === prevCanvas) {
|
|||
|
|
return prevContext;
|
|||
|
|
}
|
|||
|
|
else if (options.canvasId &&
|
|||
|
|
prevCanvas instanceof UniCanvas &&
|
|||
|
|
options.canvasId === prevCanvas.canvasId) {
|
|||
|
|
return prevContext;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
//
|
|||
|
|
return opts.canvas || opts.canvasId ? new UniContext(opts) : undefined;
|
|||
|
|
}
|
|||
|
|
static isDingTalk() {
|
|||
|
|
try {
|
|||
|
|
if (typeof dd === "object") {
|
|||
|
|
return true;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
catch (error) {
|
|||
|
|
l.log(`---- UniContext.isDingTalk.exception: `, error);
|
|||
|
|
}
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
constructor(context) {
|
|||
|
|
super(Object.assign({ apiMode: true }, (context || {})));
|
|||
|
|
//
|
|||
|
|
this.init(context || {});
|
|||
|
|
//
|
|||
|
|
l.log(`==== 【UniContext】constructor(${this.instanceId}) ====`, this.mOptions);
|
|||
|
|
}
|
|||
|
|
init(options) {
|
|||
|
|
const opts = options || {};
|
|||
|
|
// #ifdef APP-PLUS
|
|||
|
|
opts.isAppPlus = true;
|
|||
|
|
// #endif
|
|||
|
|
// #ifdef H5
|
|||
|
|
opts.isH5 = true;
|
|||
|
|
// #endif
|
|||
|
|
// #ifdef MP-WEIXIN
|
|||
|
|
opts.isWeiXin = true;
|
|||
|
|
// #endif
|
|||
|
|
// #ifdef MP-ALIPAY
|
|||
|
|
opts.isAlipay = true;
|
|||
|
|
// #endif
|
|||
|
|
//
|
|||
|
|
if (opts.isAlipay) {
|
|||
|
|
opts.isDingTalk = UniContext.isDingTalk();
|
|||
|
|
}
|
|||
|
|
//
|
|||
|
|
this.mOptions = opts;
|
|||
|
|
}
|
|||
|
|
createCanvas() {
|
|||
|
|
const context = this.mOptions;
|
|||
|
|
l.info(`【UniContext】createCanvas: create by options: `, this.mOptions);
|
|||
|
|
//
|
|||
|
|
if (!this.mCanvas) {
|
|||
|
|
if (document && typeof document.createElement === "function") {
|
|||
|
|
l.log('【UniContext】createCanvas by document.createElement("canvas")!');
|
|||
|
|
// H5环境适配
|
|||
|
|
this.mCanvas = document.createElement("canvas");
|
|||
|
|
}
|
|||
|
|
else if (context.canvas && typeof context.canvas.getContext === "function") {
|
|||
|
|
l.log("【UniContext】createCanvas by options.canvas!");
|
|||
|
|
// 新版在屏 canvas 适配,canvas必须支持获取绘制上下文才可以使用
|
|||
|
|
this.mCanvas = context.canvas;
|
|||
|
|
}
|
|||
|
|
else if (context.canvasId) {
|
|||
|
|
l.log("【UniContext】createCanvas by canvasId!");
|
|||
|
|
// 旧版在屏 canvas 适配
|
|||
|
|
this.mCanvas = new UniCanvas(this.mOptions);
|
|||
|
|
}
|
|||
|
|
else {
|
|||
|
|
l.log("【UniContext】createCanvas by uni.createOffscreenCanvas!");
|
|||
|
|
// 离屏 canvas 适配
|
|||
|
|
if (typeof uni.createOffscreenCanvas === "function") {
|
|||
|
|
this.mCanvas = uni.createOffscreenCanvas({ type: "2d" });
|
|||
|
|
}
|
|||
|
|
if (!this.mCanvas) {
|
|||
|
|
if (context.isWeiXin) {
|
|||
|
|
l.warn("================================");
|
|||
|
|
l.warn("==== 真机调试请使用 2.0 模式 ====");
|
|||
|
|
l.warn("================================");
|
|||
|
|
}
|
|||
|
|
else {
|
|||
|
|
l.warn("=======================================");
|
|||
|
|
l.warn("==== 当前环境不支持 OffscreenCanvas ====");
|
|||
|
|
l.warn("=======================================");
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
else {
|
|||
|
|
l.info("==================================");
|
|||
|
|
l.info("==== OffscreenCanvas创建成功! ====");
|
|||
|
|
l.info("==================================");
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
//
|
|||
|
|
return this.mCanvas;
|
|||
|
|
}
|
|||
|
|
/**
|
|||
|
|
* 通过 canvas 来创建 Image 对象。
|
|||
|
|
*/
|
|||
|
|
createImage(canvas, src, timeout) {
|
|||
|
|
// 注意:
|
|||
|
|
// 1. 不知道为什么,通过canvas.createImage创建的Image在小程序中第二次加载图片的时候不会触发 onload 方法;
|
|||
|
|
// 2. Image本身就可以加载网络图片,所以也不需要使用 getImageInfo来缓存;
|
|||
|
|
// 3. 网络图片可以通过添加随机参数来避免第二次无法加载的问题,本地图片该方法不可行;
|
|||
|
|
// 4. 加载完之后,不能立即将 image.src 设置为空,设置为空后图片显示黑块;
|
|||
|
|
return new Promise((resolve) => {
|
|||
|
|
const imgSrc = src.startsWith("http") ? `${src}?${new Date().getTime()}${Math.random()}` : src;
|
|||
|
|
// const canvas = uni.createOffscreenCanvas({ type: "2d" }) as any;
|
|||
|
|
const image = canvas.createImage();
|
|||
|
|
const subImagePath = imgSrc.length > 100 ? `${imgSrc.substring(0, 100)}...` : imgSrc;
|
|||
|
|
let processed = false;
|
|||
|
|
l.info(`---- canvas.createImage: ${subImagePath}`);
|
|||
|
|
image.onload = () => {
|
|||
|
|
l.info(`---- image.onload`);
|
|||
|
|
if (processed) {
|
|||
|
|
l.warn(`图片加载时间太长了,已经进行超时处理了!`);
|
|||
|
|
}
|
|||
|
|
else {
|
|||
|
|
processed = true;
|
|||
|
|
resolve(image);
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
image.onerror = (res) => {
|
|||
|
|
l.warn(res, `---- image.onerror:${subImagePath}`);
|
|||
|
|
processed = true;
|
|||
|
|
resolve(imgSrc);
|
|||
|
|
};
|
|||
|
|
//
|
|||
|
|
setTimeout(() => {
|
|||
|
|
if (!processed) {
|
|||
|
|
l.warn(`---- 图片加载超时:`);
|
|||
|
|
processed = true;
|
|||
|
|
resolve(image);
|
|||
|
|
}
|
|||
|
|
}, timeout || 3000);
|
|||
|
|
image.src = imgSrc;
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
loadImage(options) {
|
|||
|
|
const _super = Object.create(null, {
|
|||
|
|
loadImage: { get: () => super.loadImage }
|
|||
|
|
});
|
|||
|
|
return __awaiter(this, void 0, void 0, function* () {
|
|||
|
|
l.log(`---- UniContext.loadImage:`);
|
|||
|
|
const opts = typeof options === "string" ? { src: options } : options;
|
|||
|
|
if (!opts || !opts.src) {
|
|||
|
|
return null;
|
|||
|
|
}
|
|||
|
|
const canvas = this.createCanvas();
|
|||
|
|
if (canvas instanceof UniCanvas) {
|
|||
|
|
// 通过 canvasId 来创建上下文的情况;
|
|||
|
|
return canvas.loadImage(opts);
|
|||
|
|
}
|
|||
|
|
else if (document && typeof document.createElement === "function") {
|
|||
|
|
return _super.loadImage.call(this, opts.src);
|
|||
|
|
}
|
|||
|
|
else if (canvas && canvas === this.mOptions.canvas) {
|
|||
|
|
// 新版的在屏 canvas 适配;
|
|||
|
|
return this.createImage(canvas, opts.src, 0);
|
|||
|
|
}
|
|||
|
|
else if (typeof uni.createOffscreenCanvas === "function") {
|
|||
|
|
// 兼容微信小程序离屏Canvas
|
|||
|
|
return this.createImage(canvas, opts.src, 0);
|
|||
|
|
}
|
|||
|
|
else {
|
|||
|
|
return _super.loadImage.call(this, opts.src);
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
startJob(options) {
|
|||
|
|
l.log(`#### UniContext.startJob - before super.startJob:`);
|
|||
|
|
// 通过 querySelector 方式获取到 canvas后,已经直接将canvas配置到了 DzCanvas的初始化函数中了;
|
|||
|
|
// 所以底层不会再调用 createCanvas了,所以此处需要重新获取下 canvas。
|
|||
|
|
if (!this.mCanvas) {
|
|||
|
|
this.createCanvas();
|
|||
|
|
}
|
|||
|
|
if (!this.mCanvas) {
|
|||
|
|
l.warn(`#### UniContext.startJob: 未获取到有效的 canvas 实例,无法进行标签绘制!`, this.mCanvas);
|
|||
|
|
return undefined;
|
|||
|
|
}
|
|||
|
|
//
|
|||
|
|
const res = super.startJob(options);
|
|||
|
|
l.log(`#### UniContext.startJob - after super.startJob:`, res);
|
|||
|
|
if (!res)
|
|||
|
|
return res;
|
|||
|
|
//
|
|||
|
|
if (this.mCanvas && this.mCanvas instanceof UniCanvas) {
|
|||
|
|
this.mCanvas.start(res);
|
|||
|
|
}
|
|||
|
|
return Object.assign(Object.assign({}, res), { canvas: this.mCanvas });
|
|||
|
|
}
|
|||
|
|
onCanvasClear(canvas, context) {
|
|||
|
|
// if (canvas && canvas instanceof UniCanvas) {
|
|||
|
|
// canvas.draw(context as any, this.IsApiMode, true);
|
|||
|
|
// }
|
|||
|
|
}
|
|||
|
|
commitJob() {
|
|||
|
|
l.info(`---- [UniContext] DrawContext.commitJob ====> before super.commitJob:`);
|
|||
|
|
return super.commitJob().then((res) => {
|
|||
|
|
l.info(`---- [UniContext] DrawContext.commitJob ====> after super.commitJob:`, res);
|
|||
|
|
if (!res)
|
|||
|
|
return res;
|
|||
|
|
//
|
|||
|
|
const canvas = this.mCanvas;
|
|||
|
|
const jobInfo = this.jobOptions || {};
|
|||
|
|
if (canvas && canvas instanceof UniCanvas) {
|
|||
|
|
const context = canvas.getContext();
|
|||
|
|
return canvas.draw(context, !res.isPreview).then((v) => {
|
|||
|
|
if (!res.dataUrl)
|
|||
|
|
res.dataUrl = v;
|
|||
|
|
res.tempFilePath = v;
|
|||
|
|
if (!res.isPreview || jobInfo.withImageData) {
|
|||
|
|
return canvas.getImageData().then((data) => {
|
|||
|
|
res.imageData = data;
|
|||
|
|
return res;
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
else {
|
|||
|
|
return res;
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
else if (canvas && canvas === this.mOptions.canvas) {
|
|||
|
|
l.info(`---- 获取新版在屏Canvas对应的预览图片!`, this.mOptions);
|
|||
|
|
// 新版的在屏 canvas 适配;
|
|||
|
|
if (this.mOptions.isAlipay && res.isPreview && !res.dataUrl) {
|
|||
|
|
const aliCanvas = canvas;
|
|||
|
|
const funcType = typeof aliCanvas.toTempFilePath;
|
|||
|
|
l.info(`【Request】canvas.toTempFilePath: canvas.toTempFilePath = ${funcType}`);
|
|||
|
|
if (funcType === "function") {
|
|||
|
|
// const aliCanvas: Record<string, any> = this.mContext.canvas;
|
|||
|
|
return new Promise((resolve) => {
|
|||
|
|
const timer = setTimeout(() => {
|
|||
|
|
resolve(res);
|
|||
|
|
}, 3000);
|
|||
|
|
aliCanvas.toTempFilePath({
|
|||
|
|
x: 0,
|
|||
|
|
y: 0,
|
|||
|
|
width: aliCanvas.width,
|
|||
|
|
height: aliCanvas.height,
|
|||
|
|
success: (result) => {
|
|||
|
|
l.info(`【Response.success】canvas.toTempFilePath: `, result);
|
|||
|
|
res.dataUrl = result.tempFilePath;
|
|||
|
|
clearTimeout(timer);
|
|||
|
|
resolve(res);
|
|||
|
|
},
|
|||
|
|
fail: (err) => {
|
|||
|
|
l.warn(`【Response.fail】canvas.toTempFilePath: `, err);
|
|||
|
|
clearTimeout(timer);
|
|||
|
|
resolve(res);
|
|||
|
|
},
|
|||
|
|
});
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
//
|
|||
|
|
return res;
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
class BleAdapter {
|
|||
|
|
static getInstance() {
|
|||
|
|
return this._instance || (this._instance = new BleAdapter());
|
|||
|
|
}
|
|||
|
|
constructor(options) {
|
|||
|
|
this.mConnectionMap = new Map();
|
|||
|
|
this.mCharacteristicMap = new Map();
|
|||
|
|
this.mContext = options || {};
|
|||
|
|
l.log(`==== 【BleAdapter】constructor(options) ====`, this.mContext);
|
|||
|
|
// #ifdef APP-PLUS
|
|||
|
|
this.mContext.isAppPlus = true;
|
|||
|
|
// #endif
|
|||
|
|
// #ifdef H5
|
|||
|
|
this.mContext.isH5 = true;
|
|||
|
|
// #endif
|
|||
|
|
// #ifdef MP-WEIXIN
|
|||
|
|
this.mContext.isWeiXin = true;
|
|||
|
|
// #endif
|
|||
|
|
// #ifdef MP-ALIPAY
|
|||
|
|
this.mContext.isAlipay = true;
|
|||
|
|
// #endif
|
|||
|
|
//
|
|||
|
|
if (this.mContext.isAlipay) {
|
|||
|
|
this.mContext.isDingTalk = UniContext.isDingTalk();
|
|||
|
|
}
|
|||
|
|
//
|
|||
|
|
if (typeof uni.getDeviceInfo === "function") {
|
|||
|
|
this.mDeviceInfo = uni.getDeviceInfo();
|
|||
|
|
l.log(`---- deviceInfo:`, this.mDeviceInfo);
|
|||
|
|
}
|
|||
|
|
if (typeof uni.getSystemInfo === "function") {
|
|||
|
|
uni.getSystemInfo({
|
|||
|
|
success: (res) => {
|
|||
|
|
this.mSystemInfo = res;
|
|||
|
|
l.log(`---- mSystemInfo: `, res);
|
|||
|
|
},
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
get isH5() {
|
|||
|
|
return this.mContext.isH5;
|
|||
|
|
}
|
|||
|
|
get Context() {
|
|||
|
|
return this.mContext;
|
|||
|
|
}
|
|||
|
|
get platform() {
|
|||
|
|
var _a, _b, _c;
|
|||
|
|
return ((_a = this.mDeviceInfo) === null || _a === void 0 ? void 0 : _a.platform) || ((_b = this.mSystemInfo) === null || _b === void 0 ? void 0 : _b.osName) || ((_c = this.mSystemInfo) === null || _c === void 0 ? void 0 : _c.platform) || "";
|
|||
|
|
}
|
|||
|
|
isAndroid() {
|
|||
|
|
return this.platform.toLowerCase() === "android";
|
|||
|
|
}
|
|||
|
|
isHarmonyOS() {
|
|||
|
|
const osName = this.platform;
|
|||
|
|
return osName === "ohos" || osName.toLowerCase().indexOf("harmony") >= 0;
|
|||
|
|
}
|
|||
|
|
getDeviceInfo() {
|
|||
|
|
return this.mDeviceInfo;
|
|||
|
|
}
|
|||
|
|
androidAPILevel() {
|
|||
|
|
if (this.isAndroid() && this.mContext.isAppPlus) {
|
|||
|
|
const apiLevel = this.mSystemInfo ? this.mSystemInfo.osAndroidAPILevel : 0;
|
|||
|
|
if (typeof apiLevel === "number" && apiLevel > 0) {
|
|||
|
|
return apiLevel;
|
|||
|
|
}
|
|||
|
|
else {
|
|||
|
|
const Build = plus.android.importClass("android.os.Build");
|
|||
|
|
const version = Build ? Build.VERSION.SDK_INT : 0;
|
|||
|
|
return typeof version === "number" ? version : 0;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
else {
|
|||
|
|
return 0;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
requestPermissions() {
|
|||
|
|
return new Promise((resolve) => {
|
|||
|
|
const sdkVersion = this.androidAPILevel();
|
|||
|
|
if (sdkVersion >= 23) {
|
|||
|
|
const permissions = [];
|
|||
|
|
if (sdkVersion >= 31) {
|
|||
|
|
permissions.push("android.permission.BLUETOOTH_SCAN");
|
|||
|
|
permissions.push("android.permission.BLUETOOTH_CONNECT");
|
|||
|
|
}
|
|||
|
|
if (sdkVersion >= 29) {
|
|||
|
|
permissions.push("android.permission.ACCESS_FINE_LOCATION");
|
|||
|
|
}
|
|||
|
|
else {
|
|||
|
|
permissions.push("android.permission.ACCESS_COARSE_LOCATION");
|
|||
|
|
}
|
|||
|
|
l.log(`#### 【Request】plus.android.requestPermissions, sdkVersion = ${sdkVersion}`);
|
|||
|
|
plus.android.requestPermissions(permissions, (res) => {
|
|||
|
|
l.log(res, `#### 【Response.success】plus.android.requestPermissions:`);
|
|||
|
|
resolve(true);
|
|||
|
|
}, (res) => {
|
|||
|
|
l.warn(res, `#### 【Response.error】plus.android.requestPermissions:`);
|
|||
|
|
resolve(false);
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
else {
|
|||
|
|
resolve(true);
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
/**
|
|||
|
|
* 蓝牙授权认证。
|
|||
|
|
*/
|
|||
|
|
authorize(options) {
|
|||
|
|
l.log(`【BleAdapter】authorize(options)`, options, this.mContext);
|
|||
|
|
if (this.mContext.isAppPlus) {
|
|||
|
|
return this.requestPermissions().then((result) => {
|
|||
|
|
if (result) {
|
|||
|
|
return Le.success(true, options);
|
|||
|
|
}
|
|||
|
|
else {
|
|||
|
|
return Le.complete(Ee.ERROR_AUTH_FAILED, "蓝牙权限申请失败!", options, undefined);
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
else if (this.mContext.isH5) {
|
|||
|
|
return Promise.resolve(Le.success(true, options));
|
|||
|
|
}
|
|||
|
|
else if (this.mContext.isAlipay) {
|
|||
|
|
// 支付宝小程序不支持 authorize 接口
|
|||
|
|
return Promise.resolve(Le.success(true, options));
|
|||
|
|
}
|
|||
|
|
//
|
|||
|
|
return new Promise((resolve) => {
|
|||
|
|
try {
|
|||
|
|
l.info("#### 【Request】authorize ----> getSetting");
|
|||
|
|
if (typeof uni.getSetting !== "function" || typeof uni.authorize !== "function") {
|
|||
|
|
Le.success("不支持蓝牙认证相关接口!", options, resolve);
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
uni.getSetting({
|
|||
|
|
success: (res) => {
|
|||
|
|
const authSetting = res.authSetting;
|
|||
|
|
if (authSetting["scope.bluetooth"] || authSetting["bluetooth"]) {
|
|||
|
|
l.info(`####【Response.success】authorize.getSetting: 蓝牙权限获取成功!`, res);
|
|||
|
|
Le.success("蓝牙权限已授权", options, resolve);
|
|||
|
|
}
|
|||
|
|
else {
|
|||
|
|
l.info(`####【Response.success】authorize.getSetting: 蓝牙权限未授权!`, res);
|
|||
|
|
l.info(`【Request】authorize:`);
|
|||
|
|
uni.authorize({
|
|||
|
|
scope: "scope.bluetooth",
|
|||
|
|
success: (res2) => {
|
|||
|
|
l.info(`#### 【Response.success】authorize: 蓝牙权限授权成功!`, res2);
|
|||
|
|
Le.success("蓝牙权限授权成功!", options, resolve);
|
|||
|
|
},
|
|||
|
|
fail: (res2) => {
|
|||
|
|
l.warn(`#### 【Response.fail】authorize: 蓝牙权限授权失败!`, res2);
|
|||
|
|
// 如果用户拒绝了蓝牙授权申请,则需要提示用户去设置中开启蓝牙权限。
|
|||
|
|
uni.showModal({
|
|||
|
|
title: "蓝牙权限未授权",
|
|||
|
|
content: "请前往设置界面开启蓝牙权限",
|
|||
|
|
confirmText: "去设置",
|
|||
|
|
cancelText: "取消",
|
|||
|
|
showCancel: true,
|
|||
|
|
success: (modalRes) => {
|
|||
|
|
if (modalRes.confirm) {
|
|||
|
|
l.info(`#### 【Response.success】用户点击了去设置按钮!`, modalRes);
|
|||
|
|
uni.openSetting({
|
|||
|
|
success: (settingRes) => {
|
|||
|
|
if (settingRes.authSetting["scope.bluetooth"]) {
|
|||
|
|
l.info(`#### 【Response.success】蓝牙权限已授权!`, settingRes);
|
|||
|
|
Le.success("蓝牙权限已授权", options, resolve);
|
|||
|
|
}
|
|||
|
|
else {
|
|||
|
|
l.warn(`#### 【Response.success】蓝牙权限仍未授权!`, settingRes);
|
|||
|
|
Le.complete(Ee.ERROR_AUTH_FAILED, "蓝牙权限仍未授权", options, resolve);
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
fail: (settingRes) => {
|
|||
|
|
l.warn(`#### 【Response.fail】openSetting, 权限设置界面打开失败!`, settingRes);
|
|||
|
|
Le.complete(Ee.ERROR_AUTH_FAILED, "权限设置界面打开失败", options, resolve);
|
|||
|
|
},
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
else {
|
|||
|
|
l.warn(`---- 用户点击了取消按钮!`, modalRes);
|
|||
|
|
Le.complete(Ee.ERROR_AUTH_FAILED, "用户取消了蓝牙权限设置", options, resolve);
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
fail: (modalRes) => {
|
|||
|
|
l.warn(`#### 【Response.fail】showModal:`, modalRes);
|
|||
|
|
Le.complete(Ee.ERROR_AUTH_FAILED, "蓝牙权限设置弹窗打开失败", options, resolve);
|
|||
|
|
},
|
|||
|
|
});
|
|||
|
|
},
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
fail: (res) => {
|
|||
|
|
l.warn(`#### 【Response.fail】蓝牙权限获取失败!`, res);
|
|||
|
|
Le.complete(Ee.ERROR_AUTH_FAILED, res, options, resolve);
|
|||
|
|
},
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
catch (error) {
|
|||
|
|
l.warn(`#### 【Response.error】蓝牙权限获取异常!`, error);
|
|||
|
|
Le.complete(Ee.ERROR_AUTH_FAILED, "获取蓝牙权限异常,请检查网络连接或小程序设置。", options, resolve);
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
createAdapterStateChangeAction() {
|
|||
|
|
if (!this.mAdapterStateChangeAction) {
|
|||
|
|
this.mAdapterStateChangeAction = (res) => {
|
|||
|
|
l.info("==== onBluetoothAdapterStateChange: ====");
|
|||
|
|
l.info(JSON.stringify(res));
|
|||
|
|
if (typeof res.available === "string") {
|
|||
|
|
res.available = res.available === "true";
|
|||
|
|
}
|
|||
|
|
if (res.available === false && this.mConnectionMap.size > 0) {
|
|||
|
|
for (const item of this.mConnectionMap.values()) {
|
|||
|
|
item.connected = false;
|
|||
|
|
if (typeof item.connectStateChange === "function") {
|
|||
|
|
item.connectStateChange({ deviceId: item.deviceId, connected: false });
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
if (typeof this.mAdapterStateChange === "function") {
|
|||
|
|
this.mAdapterStateChange(res);
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
const adapter = this.Context.bleAdapter;
|
|||
|
|
if (adapter && typeof adapter.onBluetoothAdapterStateChange === "function") {
|
|||
|
|
adapter.onBluetoothAdapterStateChange(this.mAdapterStateChangeAction);
|
|||
|
|
}
|
|||
|
|
else {
|
|||
|
|
uni.onBluetoothAdapterStateChange(this.mAdapterStateChangeAction);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
openAdapter(options) {
|
|||
|
|
const opts = options || {};
|
|||
|
|
const isOpened = this.mIsAdapterOpened;
|
|||
|
|
l.log(`【BleAdapter】openAdapter(isAdapterOpened: ${isOpened}, force: ${opts.force})`, options);
|
|||
|
|
return new Promise((resolve) => {
|
|||
|
|
if (this.mIsAdapterOpened && !opts.force) {
|
|||
|
|
l.log(`---- 蓝牙适配器已经打开!`);
|
|||
|
|
Le.success("蓝牙适配器已打开!", options, resolve);
|
|||
|
|
}
|
|||
|
|
else if (this.mContext.isH5) {
|
|||
|
|
// 在浏览器中调用 uni.openBluetoothAdapter 会抛异常,并且也无法try cache,所以直接返回失败即可。
|
|||
|
|
Le.complete(Ee.ERROR_UN_SUPPORTED, "H5环境中不支持蓝牙操作!", options, resolve);
|
|||
|
|
}
|
|||
|
|
else {
|
|||
|
|
this.authorize({
|
|||
|
|
complete: () => {
|
|||
|
|
try {
|
|||
|
|
l.info("#### 【Request】openBluetoothAdapter:", options);
|
|||
|
|
uni.openBluetoothAdapter({
|
|||
|
|
success: (openRes) => {
|
|||
|
|
this.mIsAdapterOpened = true;
|
|||
|
|
l.info(`#### 【Response.success】openBluetoothAdapter!`, openRes);
|
|||
|
|
//
|
|||
|
|
if (!this.mAdapterStateChangeAction) {
|
|||
|
|
this.createAdapterStateChangeAction();
|
|||
|
|
}
|
|||
|
|
//
|
|||
|
|
Le.success(openRes, options, resolve);
|
|||
|
|
},
|
|||
|
|
fail: (res) => {
|
|||
|
|
const errCode = res.errCode || res.code || 0;
|
|||
|
|
if (errCode <= 0) {
|
|||
|
|
l.info(`#### 【Response.fail】蓝牙适配器已打开,errCode = ${errCode}`, res);
|
|||
|
|
Le.success(res, options, resolve);
|
|||
|
|
}
|
|||
|
|
else {
|
|||
|
|
l.warn("#### 【Response.fail】openBluetoothAdapter:", res);
|
|||
|
|
if (res.errCode === 3 || res.errno === 3) {
|
|||
|
|
// 如果用户拒绝了蓝牙授权申请,则需要提示用户去设置中开启蓝牙权限。
|
|||
|
|
Le.complete(Ee.ERROR_OPEN_ADAPTER, "请在系统的应用设置里面找到HBuilder,打开权限管理,检查蓝牙、附近设备等权限是否已打开。如果已经打开,可以尝试先拒绝,然后重新允许。", options, resolve);
|
|||
|
|
}
|
|||
|
|
else {
|
|||
|
|
Le.complete(Ee.ERROR_OPEN_ADAPTER, res, options, resolve);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
catch (error) {
|
|||
|
|
l.warn("####【Exception】openBluetoothAdapter:", error);
|
|||
|
|
Le.complete(Ee.ERROR_OPEN_ADAPTER, error, options, resolve);
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
fail: (res) => {
|
|||
|
|
Le.onResult(res, options, resolve);
|
|||
|
|
},
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
resetAllEventListener() {
|
|||
|
|
if (typeof uni.offBluetoothAdapterStateChange === "function") {
|
|||
|
|
l.info(`#### uni.offBluetoothAdapterStateChange:`);
|
|||
|
|
uni.offBluetoothAdapterStateChange();
|
|||
|
|
//
|
|||
|
|
if (this.mAdapterStateChangeAction) {
|
|||
|
|
this.mAdapterStateChangeAction = undefined;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
// 关闭蓝牙适配器前停止特征值监听事件
|
|||
|
|
this.resetCharacteristicValueChange();
|
|||
|
|
}
|
|||
|
|
closeAdapter(options) {
|
|||
|
|
return __awaiter(this, void 0, void 0, function* () {
|
|||
|
|
l.info("### 【Request】: closeBluetoothAdapter: ", options);
|
|||
|
|
// 先断开所有已连接设备,在 lpapi中可能关闭的不彻底
|
|||
|
|
if (this.mConnectionMap.size > 0) {
|
|||
|
|
for (const item of this.mConnectionMap.values()) {
|
|||
|
|
yield this.disconnect({ deviceId: item.deviceId });
|
|||
|
|
}
|
|||
|
|
this.mConnectionMap.clear();
|
|||
|
|
}
|
|||
|
|
//
|
|||
|
|
return new Promise((resolve) => {
|
|||
|
|
this.resetAllEventListener();
|
|||
|
|
uni.closeBluetoothAdapter({
|
|||
|
|
success: (res) => {
|
|||
|
|
l.info(`#### 【Response.success】closeBluetoothAdapter:`, res);
|
|||
|
|
},
|
|||
|
|
fail: (res) => {
|
|||
|
|
l.warn(`#### 【Response.fail】closeBluetoothAdapter:`, res);
|
|||
|
|
},
|
|||
|
|
complete: (res) => {
|
|||
|
|
// 蓝牙适配器关闭后,打印机会断开链接,此时需要通知下LPAPI
|
|||
|
|
this.mIsAdapterOpened = false;
|
|||
|
|
resolve({ statusCode: Ee.OK, resultInfo: res });
|
|||
|
|
},
|
|||
|
|
});
|
|||
|
|
});
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
resetAdapter(options) {
|
|||
|
|
const opts = typeof options === "function" ? { callback: options } : options || {};
|
|||
|
|
return this.closeAdapter().then(() => {
|
|||
|
|
// 重新打开蓝牙适配器。
|
|||
|
|
return this.openAdapter().then((res) => {
|
|||
|
|
return Le.onResult(res, opts);
|
|||
|
|
});
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
startDiscovery(options) {
|
|||
|
|
const opts = options || {};
|
|||
|
|
const onSuccessCallback = (result) => {
|
|||
|
|
if (result.statusCode === Ee.OK) {
|
|||
|
|
const scanInterval = opts.interval || 200;
|
|||
|
|
return new Promise((resolve) => {
|
|||
|
|
l.info("### 【Request】startBluetoothDevicesDiscovery: ", options);
|
|||
|
|
uni.startBluetoothDevicesDiscovery({
|
|||
|
|
interval: scanInterval,
|
|||
|
|
allowDuplicatesKey: true,
|
|||
|
|
success: (scanRes) => {
|
|||
|
|
this.mAdapterStateChange = opts.adapterStateChange;
|
|||
|
|
l.info(`#### 【Response.success】: startBluetoothDevicesDiscovery:`, scanRes);
|
|||
|
|
if (this.mDeviceGetTimer && this.mDeviceGetTimer > 0) {
|
|||
|
|
clearInterval(this.mDeviceGetTimer);
|
|||
|
|
}
|
|||
|
|
this.mDeviceGetTimer = setInterval(() => {
|
|||
|
|
uni.getBluetoothDevices({
|
|||
|
|
success: (res) => {
|
|||
|
|
const devices = !res.devices && Array.isArray(res) ? res : res.devices || [];
|
|||
|
|
// 过滤掉没有名称的设备
|
|||
|
|
const filters = devices.filter((v) => !!(v.name || v.localName));
|
|||
|
|
if (filters.length > 0) {
|
|||
|
|
// logger.log(`---- onBluetoothDeviceFound:`, res);
|
|||
|
|
opts.deviceFound && opts.deviceFound(filters);
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
});
|
|||
|
|
}, scanInterval);
|
|||
|
|
//
|
|||
|
|
if (typeof uni.getBluetoothDevices === "function") {
|
|||
|
|
uni.getBluetoothDevices({
|
|||
|
|
success: (res) => {
|
|||
|
|
// logger.log(`#### 【Response.success: getBluetoothDevices:`);
|
|||
|
|
const devices = !res.devices && Array.isArray(res) ? res : res.devices || [];
|
|||
|
|
// 过滤掉没有名称的设备
|
|||
|
|
const filters = devices.filter((v) => !!(v.name || v.localName));
|
|||
|
|
if (filters.length > 0) {
|
|||
|
|
opts.deviceFound && opts.deviceFound(filters);
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
//
|
|||
|
|
Le.success(scanRes, options, resolve);
|
|||
|
|
},
|
|||
|
|
fail: (res) => {
|
|||
|
|
l.warn(`#### 【Response.fail】: startBluetoothDevicesDiscovery:`, JSON.stringify(res));
|
|||
|
|
Le.fail(res, options, resolve);
|
|||
|
|
},
|
|||
|
|
});
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
else {
|
|||
|
|
l.warn("========== 蓝牙适配器打开失败! ==========");
|
|||
|
|
return Le.onResult(result, options);
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
// 在Uni中重置蓝牙适配器后,倒是可以自动断开设备链接了,但是特征值的监听无法取消,重新监听特征值后,会接收到很多无效的数据。
|
|||
|
|
// 所以最好还是不要重置适配器了
|
|||
|
|
if (opts.resetAdapter) {
|
|||
|
|
return this.resetAdapter().then(onSuccessCallback);
|
|||
|
|
}
|
|||
|
|
else {
|
|||
|
|
return this.openAdapter().then(onSuccessCallback);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
stopDiscovery(options) {
|
|||
|
|
return new Promise((resolve) => {
|
|||
|
|
// 停止蓝牙搜索
|
|||
|
|
l.info(`###【Request】stopBluetoothDevicesDiscovery:`);
|
|||
|
|
uni.stopBluetoothDevicesDiscovery({
|
|||
|
|
success: (res) => {
|
|||
|
|
l.info(`###【Response.success】stopBluetoothDevicesDiscovery:`);
|
|||
|
|
Le.success(res, options, resolve);
|
|||
|
|
// 停止设备检测;
|
|||
|
|
// 在 UniApp中 onBluetoothDeviceFound 是全局的,注册后无法取消,暂时先用 getBluetoothDevices方法试试。
|
|||
|
|
if (this.mDeviceGetTimer && this.mDeviceGetTimer > 0) {
|
|||
|
|
clearInterval(this.mDeviceGetTimer);
|
|||
|
|
this.mDeviceGetTimer = 0;
|
|||
|
|
}
|
|||
|
|
// 停止搜索后,支付宝小程序不会触发该回调,所以手动触发下。
|
|||
|
|
this.mAdapterStateChange && this.mAdapterStateChange({ discovering: false });
|
|||
|
|
this.mAdapterStateChange = undefined;
|
|||
|
|
},
|
|||
|
|
fail: (res) => {
|
|||
|
|
l.warn(`###【Response.fail】stopBluetoothDevicesDiscovery:`, JSON.stringify(res));
|
|||
|
|
Le.fail(res, options, resolve);
|
|||
|
|
},
|
|||
|
|
});
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
getFoundDevices(options) {
|
|||
|
|
return new Promise((resolve) => {
|
|||
|
|
l.info("####【Request】getBluetoothDevices:", options);
|
|||
|
|
uni.getBluetoothDevices({
|
|||
|
|
success: (res) => {
|
|||
|
|
l.info("####【Response.success】getBluetoothDevices:", res);
|
|||
|
|
const devices = !res.devices && Array.isArray(res) ? res : res.devices || [];
|
|||
|
|
// 过滤掉没有名称的设备
|
|||
|
|
const filters = devices.filter((v) => !!(v.name || v.localName));
|
|||
|
|
Le.success(filters, options, resolve);
|
|||
|
|
},
|
|||
|
|
fail: (res) => {
|
|||
|
|
l.warn("####【Response.fail】getBluetoothDevices:", res);
|
|||
|
|
Le.fail([], options, resolve);
|
|||
|
|
},
|
|||
|
|
});
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
createConnectionStateChangeAction() {
|
|||
|
|
if (!this.mConnectionStateChangeAction) {
|
|||
|
|
this.mConnectionStateChangeAction = (resp) => {
|
|||
|
|
l.info(`========== onConnectionStateChange ==========`);
|
|||
|
|
l.info(JSON.stringify(resp));
|
|||
|
|
for (const item of this.mConnectionMap.values()) {
|
|||
|
|
if (resp.deviceId === item.deviceId) {
|
|||
|
|
if (typeof item.connectStateChange === "function") {
|
|||
|
|
item.connectStateChange(resp);
|
|||
|
|
}
|
|||
|
|
item.connected = resp.connected;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
uni.onBLEConnectionStateChange(this.mConnectionStateChangeAction);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
connect(options) {
|
|||
|
|
const deviceId = options.deviceId || "";
|
|||
|
|
return new Promise((resolve) => {
|
|||
|
|
this.openAdapter()
|
|||
|
|
.then(() => {
|
|||
|
|
l.info(`####【Request】createConnection:`, options);
|
|||
|
|
const connectOptions = {
|
|||
|
|
deviceId: deviceId,
|
|||
|
|
success: (res) => {
|
|||
|
|
l.info(`---- 【Response.success】createConnection:`, res);
|
|||
|
|
this.mCharacteristicMap.clear();
|
|||
|
|
// 监控打印机连接状态
|
|||
|
|
if (!this.mConnectionStateChangeAction) {
|
|||
|
|
this.createConnectionStateChangeAction();
|
|||
|
|
}
|
|||
|
|
this.mConnectionMap.set(deviceId, {
|
|||
|
|
deviceId: deviceId,
|
|||
|
|
connected: true,
|
|||
|
|
connectStateChange: options.connectionStateChange,
|
|||
|
|
});
|
|||
|
|
// 经测试:在有些环境下,打印机连接成功之后不能立马进行后续操作,否则无效;
|
|||
|
|
// IOS环境下,DF2需要200毫秒延时,DF4 100毫秒足够了,保险起见,默认统一设置300毫秒
|
|||
|
|
let awaitTimes = 300;
|
|||
|
|
if (this.mContext.isAppPlus) {
|
|||
|
|
l.log("============== APP-PLUS ============");
|
|||
|
|
if (this.isAndroid()) {
|
|||
|
|
// 在安卓环境下,打印机链接成功后大概要等待800毫秒才可以进行后续操作,保险起见,此处设置1000毫秒的延时;
|
|||
|
|
awaitTimes = 1000;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
setTimeout(() => {
|
|||
|
|
Le.success(res, options, resolve);
|
|||
|
|
}, awaitTimes);
|
|||
|
|
},
|
|||
|
|
fail: (res) => {
|
|||
|
|
l.warn(`---- 【Response.fail】createConnection:`, JSON.stringify(res));
|
|||
|
|
let errCode = res.errCode || res.code;
|
|||
|
|
if (errCode === 10010) {
|
|||
|
|
errCode = -1;
|
|||
|
|
}
|
|||
|
|
// 支付宝小程序中既没有errCode 也没有 code
|
|||
|
|
if (typeof errCode === "number" && errCode <= 0) {
|
|||
|
|
// this.mConnected = true;
|
|||
|
|
// this.mConnectStatusMap.set(deviceId, true);
|
|||
|
|
if (!this.mConnectionStateChangeAction) {
|
|||
|
|
this.createConnectionStateChangeAction();
|
|||
|
|
}
|
|||
|
|
this.mConnectionMap.set(deviceId, {
|
|||
|
|
deviceId: deviceId,
|
|||
|
|
connected: true,
|
|||
|
|
connectStateChange: options.connectionStateChange,
|
|||
|
|
});
|
|||
|
|
Le.success(res, options, resolve);
|
|||
|
|
}
|
|||
|
|
else {
|
|||
|
|
Le.complete(Ee.ERROR_CONNECT_FAILED, res, options, resolve);
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
};
|
|||
|
|
if (!this.mContext.isAlipay && typeof options.timeout === "number") {
|
|||
|
|
// 支付宝小程序不能配置 timeout,否则可能会出现链接不上的问题。
|
|||
|
|
connectOptions.timeout = options.timeout;
|
|||
|
|
}
|
|||
|
|
//
|
|||
|
|
if (this.mContext.isAlipay) {
|
|||
|
|
my.connectBLEDevice(connectOptions);
|
|||
|
|
}
|
|||
|
|
else {
|
|||
|
|
uni.createBLEConnection(connectOptions);
|
|||
|
|
}
|
|||
|
|
})
|
|||
|
|
.catch((error) => {
|
|||
|
|
l.warn("----【Exception】createConnection:", error);
|
|||
|
|
Le.complete(Ee.ERROR_CONNECT_FAILED, error, options);
|
|||
|
|
});
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
resetCharacteristicValueChange() {
|
|||
|
|
l.info(`#### ===== 重置已连接设备的所有特征值变化通知事件 ===== ####`);
|
|||
|
|
if (this.mCharacteristicValueChangeMap) {
|
|||
|
|
this.mCharacteristicValueChangeMap.clear();
|
|||
|
|
//
|
|||
|
|
if (typeof uni.offBLECharacteristicValueChange === "function") {
|
|||
|
|
uni.offBLECharacteristicValueChange();
|
|||
|
|
this.mCharacteristicValueChangeMap = undefined;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
disconnect(options) {
|
|||
|
|
return __awaiter(this, void 0, void 0, function* () {
|
|||
|
|
const opts = typeof options === "string" ? { deviceId: options } : options || {};
|
|||
|
|
const deviceId = opts.deviceId || "";
|
|||
|
|
const connectInfo = deviceId ? this.mConnectionMap.get(deviceId) : undefined;
|
|||
|
|
//
|
|||
|
|
if (!deviceId) {
|
|||
|
|
l.warn(`### 【Request】disconnect: 参数错误,deviceId = ${deviceId}`, options);
|
|||
|
|
return Promise.resolve(Le.complete(Ee.ERROR_PARAM, "参数错误", options));
|
|||
|
|
}
|
|||
|
|
if (!connectInfo) {
|
|||
|
|
l.info(`#### 设备未连接!`);
|
|||
|
|
return Le.success("设备未连接", options);
|
|||
|
|
}
|
|||
|
|
//
|
|||
|
|
return new Promise((resolve) => {
|
|||
|
|
const api = uni;
|
|||
|
|
// 重置连接状态变化通知事件
|
|||
|
|
if (typeof uni.offBLEConnectionStateChange === "function") {
|
|||
|
|
// 微信小程序、飞书小程序适配;
|
|||
|
|
l.info(`---- offBLEConnectionStateChange:`);
|
|||
|
|
uni.offBLEConnectionStateChange();
|
|||
|
|
this.mConnectionStateChangeAction = undefined;
|
|||
|
|
}
|
|||
|
|
else if (typeof api.offBLEConnectionStateChanged === "function") {
|
|||
|
|
// 支付宝/钉钉小程序适配;
|
|||
|
|
l.info(`---- offBLEConnectionStateChanged:`);
|
|||
|
|
api.offBLEConnectionStateChanged();
|
|||
|
|
this.mConnectionStateChangeAction = undefined;
|
|||
|
|
}
|
|||
|
|
//
|
|||
|
|
l.info(`#### ===== 重置已连接设备的所有特征值变化通知事件 ===== ####`);
|
|||
|
|
this.resetCharacteristicValueChange();
|
|||
|
|
//
|
|||
|
|
l.info(`####【Request】closeConnection: ${opts.name || deviceId}`, options);
|
|||
|
|
if (!this.mIsAdapterOpened || !connectInfo.connected) {
|
|||
|
|
this.mConnectionMap.delete(deviceId);
|
|||
|
|
l.info(`#### 设备未连接!`, connectInfo);
|
|||
|
|
Le.success("设备未连接", options, resolve);
|
|||
|
|
}
|
|||
|
|
else {
|
|||
|
|
const closeOptions = {
|
|||
|
|
deviceId: deviceId,
|
|||
|
|
success: (res) => {
|
|||
|
|
l.log(`####【Response.success】closeConnection:`, res);
|
|||
|
|
connectInfo.connected = false;
|
|||
|
|
this.mConnectionMap.delete(deviceId);
|
|||
|
|
this.mCharacteristicMap.clear();
|
|||
|
|
Le.success(res, options, resolve);
|
|||
|
|
},
|
|||
|
|
fail: (res) => {
|
|||
|
|
connectInfo.connected = false;
|
|||
|
|
l.warn(`####【Response.fail】closeConnection:`, res);
|
|||
|
|
Le.fail(res, options, resolve);
|
|||
|
|
},
|
|||
|
|
};
|
|||
|
|
if (this.mContext.isAlipay) {
|
|||
|
|
my.disconnectBLEDevice(closeOptions).catch((error) => {
|
|||
|
|
l.warn(`#### 【Response.Exception】closeConnection:`, error);
|
|||
|
|
return Le.complete(Ee.ERROR_EXCEPTION, error, options);
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
else {
|
|||
|
|
uni.closeBLEConnection(closeOptions);
|
|||
|
|
}
|
|||
|
|
// 特殊情况下设备断开连接无响应
|
|||
|
|
setTimeout(() => {
|
|||
|
|
if (connectInfo.connected) {
|
|||
|
|
connectInfo.connected = false;
|
|||
|
|
l.warn(`####【Response.timeout】closeConnection:`);
|
|||
|
|
Le.complete(Ee.ERROR_RESPONSE_TIMEOUT, undefined, options, resolve);
|
|||
|
|
}
|
|||
|
|
}, 5000);
|
|||
|
|
}
|
|||
|
|
}).catch((error) => {
|
|||
|
|
this.mConnectionMap.delete(deviceId);
|
|||
|
|
l.warn(`#### 【Response.Exception】closeConnection:`, error);
|
|||
|
|
return Le.complete(Ee.ERROR_EXCEPTION, error, options);
|
|||
|
|
});
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
getConnectedDevices(options) {
|
|||
|
|
if (typeof uni.getConnectedBluetoothDevices !== "function") {
|
|||
|
|
l.warn(`#### 【Request】getConnectedDevices: 当前环境不支持获取已连接设备!`);
|
|||
|
|
return Promise.resolve(Le.complete(Ee.ERROR_UN_SUPPORTED, [], options));
|
|||
|
|
}
|
|||
|
|
//
|
|||
|
|
return new Promise((resolve) => {
|
|||
|
|
this.openAdapter().then((res) => {
|
|||
|
|
if (res.statusCode === 0) {
|
|||
|
|
l.info(`###【Request】getConnectedDevices:`);
|
|||
|
|
let devices = undefined;
|
|||
|
|
uni.getConnectedBluetoothDevices({
|
|||
|
|
services: [],
|
|||
|
|
success: (res) => {
|
|||
|
|
l.info(res, `#### 【Response.success】: getConnectedBluetoothDevices`);
|
|||
|
|
devices = (res === null || res === void 0 ? void 0 : res.devices) || [];
|
|||
|
|
Le.success(devices, options, resolve);
|
|||
|
|
},
|
|||
|
|
fail: (res) => {
|
|||
|
|
l.info(res, `#### 【Response.fail】: getConnectedBluetoothDevices`);
|
|||
|
|
devices = [];
|
|||
|
|
Le.fail([], options, resolve);
|
|||
|
|
},
|
|||
|
|
});
|
|||
|
|
setTimeout(() => {
|
|||
|
|
if (!devices) {
|
|||
|
|
Le.fail([], options, resolve);
|
|||
|
|
}
|
|||
|
|
}, 100);
|
|||
|
|
}
|
|||
|
|
else {
|
|||
|
|
Le.fail([], options, resolve);
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
setBleMtu(options) {
|
|||
|
|
const deviceId = options.deviceId || "";
|
|||
|
|
return new Promise((resolve) => {
|
|||
|
|
const platform = this.platform;
|
|||
|
|
if (Le.isIOS(platform)) {
|
|||
|
|
// IOS 系统使用不做处理,使用默认的MTU大小;
|
|||
|
|
resolve({ status: -1 });
|
|||
|
|
}
|
|||
|
|
else {
|
|||
|
|
const writeWaits = options.waits || 0;
|
|||
|
|
let delayTime = 0;
|
|||
|
|
if (this.mContext.isAppPlus) {
|
|||
|
|
if (options.chipType === "DF5" || options.mtu >= 500) {
|
|||
|
|
delayTime = 0;
|
|||
|
|
}
|
|||
|
|
else {
|
|||
|
|
delayTime = 5;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
const waits = writeWaits + delayTime;
|
|||
|
|
let supportMtu = true;
|
|||
|
|
if (this.mContext.isAlipay) {
|
|||
|
|
// 支付宝小程序下的钉钉小程序不支持 setBLEMTU 设置。
|
|||
|
|
supportMtu = my && typeof my.setBLEMTU === "function";
|
|||
|
|
}
|
|||
|
|
else {
|
|||
|
|
supportMtu = typeof uni.setBLEMTU === "function";
|
|||
|
|
}
|
|||
|
|
l.info(`####【Request】setBleMTU: { deviceId: ${deviceId}, mtu: ${options.mtu}, platform: ${platform}}, supported: ${supportMtu}`);
|
|||
|
|
// 在支付宝小程序中,不管是不是DF4芯片,必须要写上下MTU(即便回调失败),后续才可以正常发送数据,否则打印异常。
|
|||
|
|
// 支付宝小程序虽然协商失败,但是必须要协商;
|
|||
|
|
// 钉钉小程序不支持MTU协商
|
|||
|
|
if (supportMtu) {
|
|||
|
|
let resMtu = 0;
|
|||
|
|
if (typeof uni.onBLEMTUChange === "function") {
|
|||
|
|
l.info(`####【Request】onBLEMTUChange:`);
|
|||
|
|
uni.onBLEMTUChange((res) => {
|
|||
|
|
l.info(`---- onBleMTUChange: ${res.mtu}`, res);
|
|||
|
|
resMtu = res.mtu;
|
|||
|
|
uni.offBLEMTUChange();
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
uni.setBLEMTU({
|
|||
|
|
deviceId: deviceId,
|
|||
|
|
mtu: 512,
|
|||
|
|
success: (resp) => {
|
|||
|
|
l.info(`#### 【Response.success】: setBleMTU`);
|
|||
|
|
l.info(JSON.stringify(resp));
|
|||
|
|
const mtu = resp.mtu > 0 ? resp.mtu : resMtu;
|
|||
|
|
if (mtu > 0) {
|
|||
|
|
// 小程序中会执行这行代码
|
|||
|
|
resolve({ status: 0, mtu: mtu > options.mtu ? options.mtu : mtu, waits: waits });
|
|||
|
|
}
|
|||
|
|
else {
|
|||
|
|
// uniapp中不支持 getBLEMTU,小程序中可以通过 onBLEMTUChange 来获取,所以此处暂时直接返回,后期根据实际情况在进行调整。
|
|||
|
|
// uniapp中不支持 onBLEMTUChange 回调,所以会执行这行代码。
|
|||
|
|
resolve({ status: 0, waits: waits });
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
fail: (resp) => {
|
|||
|
|
l.warn(`#### 【Response.fail】 setBLEMtu: MTU协商失败,正在尝试读取当前mtu大小`, resp);
|
|||
|
|
uni.getBLEMTU({
|
|||
|
|
deviceId: deviceId,
|
|||
|
|
success: (getRes) => {
|
|||
|
|
l.info(`#### 【Response.success】: getBleMTU`, getRes);
|
|||
|
|
if (getRes.mtu && getRes.mtu > options.mtu) {
|
|||
|
|
resolve({ status: 0, mtu: resp.mtu || options.mtu, waits: waits });
|
|||
|
|
}
|
|||
|
|
else if (options.chipType && !options.required) {
|
|||
|
|
// DF4及之后的蓝牙芯片不需要进行MTU的协商处理;
|
|||
|
|
resolve({ status: -1, waits: waits });
|
|||
|
|
}
|
|||
|
|
else {
|
|||
|
|
resolve({ status: 2, waits: waits });
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
fail: (getRes) => {
|
|||
|
|
l.warn(`#### 【Response.fail】: getBleMTU`, getRes);
|
|||
|
|
resolve({ status: 2, waits: waits });
|
|||
|
|
},
|
|||
|
|
});
|
|||
|
|
},
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
else {
|
|||
|
|
if (options.chipType && !options.required) {
|
|||
|
|
l.info(`####【Response.unsupported】setBleMTU: 当前环境不支持MTU协商!DFType = ${options.chipType}`);
|
|||
|
|
// DF4及之后的蓝牙芯片不需要进行MTU的协商处理;
|
|||
|
|
resolve({ status: -1, waits: waits });
|
|||
|
|
}
|
|||
|
|
else {
|
|||
|
|
l.warn(`####【Response.unsupported】setBleMTU: 当前环境不支持MTU协商!DFType = ${options.chipType}`);
|
|||
|
|
resolve({ status: 1, waits: waits });
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
getGATTServices(options) {
|
|||
|
|
const deviceId = options.deviceId || "";
|
|||
|
|
return new Promise((resolve) => {
|
|||
|
|
l.info(`#### 【Request】getGattService: ${deviceId}`);
|
|||
|
|
uni.getBLEDeviceServices({
|
|||
|
|
deviceId: deviceId,
|
|||
|
|
success: (resp) => {
|
|||
|
|
l.info(`#### 【Response.success】getGattService: ${deviceId}`);
|
|||
|
|
// logger.info(JSON.stringify(resp));
|
|||
|
|
const services = (resp.services || []).map((v) => {
|
|||
|
|
return Object.assign(v, { uuid: v.uuid || v.serviceId || "" });
|
|||
|
|
});
|
|||
|
|
if (services.length <= 0) {
|
|||
|
|
l.warn(`---- 服务列表为空:${JSON.stringify(resp)}`);
|
|||
|
|
}
|
|||
|
|
Le.successResult(services, options, resolve);
|
|||
|
|
},
|
|||
|
|
fail: (resp) => {
|
|||
|
|
l.warn(`#### 【Response.fail】getGattService: ${deviceId}`);
|
|||
|
|
l.warn(JSON.stringify(resp));
|
|||
|
|
Le.failResult([], options, resolve);
|
|||
|
|
},
|
|||
|
|
});
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
getGATTCharacteristics(options) {
|
|||
|
|
const deviceId = options.deviceId || "";
|
|||
|
|
const serviceId = options.serviceId || "";
|
|||
|
|
return new Promise((resolve) => {
|
|||
|
|
l.info(`#### 【Request】getCharacteristicList(${options.serviceId})`);
|
|||
|
|
const cacheList = serviceId ? this.mCharacteristicMap.get(serviceId) : undefined;
|
|||
|
|
if (cacheList) {
|
|||
|
|
l.info(`#### 【Response.Cache】getCharacteristicList(${options.serviceId})`);
|
|||
|
|
Le.successResult(cacheList, options, resolve);
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
uni.getBLEDeviceCharacteristics({
|
|||
|
|
deviceId: deviceId,
|
|||
|
|
serviceId: serviceId,
|
|||
|
|
success: (res) => {
|
|||
|
|
l.info(`#### 【Response.success】getCharacteristicList(${options.serviceId})`);
|
|||
|
|
// logger.log(JSON.stringify(res));
|
|||
|
|
const characterList = res.characteristics || [];
|
|||
|
|
if (characterList.length <= 0) {
|
|||
|
|
l.warn(`---- 特征值列表为空:${JSON.stringify(res)}`);
|
|||
|
|
}
|
|||
|
|
const list = characterList.map((v) => {
|
|||
|
|
const prop = v.properties || {};
|
|||
|
|
let propValue = 0;
|
|||
|
|
if (prop.read)
|
|||
|
|
propValue |= Qe.READ;
|
|||
|
|
if (prop.write) {
|
|||
|
|
if (prop.writeNoResponse)
|
|||
|
|
propValue |= Qe.WRITE_NO_RESPONSE;
|
|||
|
|
else
|
|||
|
|
propValue |= Qe.WRITE;
|
|||
|
|
}
|
|||
|
|
if (prop.notify)
|
|||
|
|
propValue |= Qe.NOTIFY;
|
|||
|
|
if (prop.indicate)
|
|||
|
|
propValue |= Qe.INDICATE;
|
|||
|
|
return Object.assign(v, {
|
|||
|
|
serviceId: options.serviceId,
|
|||
|
|
uuid: v.uuid || v.characteristicId || "",
|
|||
|
|
properties: propValue,
|
|||
|
|
});
|
|||
|
|
});
|
|||
|
|
if (list.length > 0) {
|
|||
|
|
this.mCharacteristicMap.set(serviceId, list);
|
|||
|
|
}
|
|||
|
|
Le.successResult(list, options, resolve);
|
|||
|
|
},
|
|||
|
|
fail: (res) => {
|
|||
|
|
l.warn(`#### 【Response.fail】getCharacteristicList(${options.serviceId})`, res);
|
|||
|
|
Le.failResult([], options, resolve);
|
|||
|
|
},
|
|||
|
|
});
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
onBLECharacteristicValueChange(characterId, callback) {
|
|||
|
|
// 如果不支持关闭ble特征值变化通知的功能,则统一注册,并且只注册一次后,根据实际需要进行分发处理。
|
|||
|
|
l.log(`#### 【Register】CharacterValueChanged[${Le.getShortUUID(characterId)}]`);
|
|||
|
|
if (!this.mCharacteristicValueChangeMap) {
|
|||
|
|
l.info(`#### ===== 开始监视已连接设备的所有特征值变化通知事件 ===== ####`);
|
|||
|
|
this.mCharacteristicValueChangeMap = new Map();
|
|||
|
|
const action = (resp) => {
|
|||
|
|
if (this.mContext.isDingTalk) {
|
|||
|
|
// 钉钉小程序信用流控返回相关数据的时候,特征值 UUID 是错误的,需要特殊处理下。
|
|||
|
|
const map = this.mCharacteristicValueChangeMap;
|
|||
|
|
const keyList = map ? Array.from(map.keys()) : [];
|
|||
|
|
let respId = keyList.find((v) => Le.isMatchedUUID(v, resp.characteristicId));
|
|||
|
|
const data = typeof resp.value === "string"
|
|||
|
|
? Le.hex16ToArrayBuffer(resp.value)
|
|||
|
|
: new Uint8Array(resp.value);
|
|||
|
|
if ((data[0] === 1 && data.length === 2) || (data[0] === 2 && data.length === 3)) {
|
|||
|
|
const fcId = keyList.find((v) => Le.isMatchedUUID("FF03", v));
|
|||
|
|
if (fcId)
|
|||
|
|
respId = fcId;
|
|||
|
|
}
|
|||
|
|
const callback = respId && map ? map.get(respId) : undefined;
|
|||
|
|
if (typeof callback === "function") {
|
|||
|
|
callback(resp);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
else if (this.mCharacteristicValueChangeMap) {
|
|||
|
|
for (const item of this.mCharacteristicValueChangeMap) {
|
|||
|
|
if (Le.isMatchedUUID(item[0], resp.characteristicId)) {
|
|||
|
|
item[1](resp);
|
|||
|
|
break;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
//
|
|||
|
|
const adapter = this.Context.bleAdapter;
|
|||
|
|
if (adapter && typeof adapter.onBLECharacteristicValueChange === "function") {
|
|||
|
|
adapter.onBLECharacteristicValueChange(action);
|
|||
|
|
}
|
|||
|
|
else {
|
|||
|
|
uni.onBLECharacteristicValueChange(action);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
//
|
|||
|
|
this.mCharacteristicValueChangeMap.set(characterId, callback);
|
|||
|
|
}
|
|||
|
|
offBLECharacteristicValueChange(cid) {
|
|||
|
|
const map = this.mCharacteristicValueChangeMap;
|
|||
|
|
if (!cid || !map)
|
|||
|
|
return;
|
|||
|
|
//
|
|||
|
|
const shortId = Le.getShortUUID(cid);
|
|||
|
|
const remainIds = Array.from(map.keys()).map((v) => Le.getShortUUID(v));
|
|||
|
|
map.delete(cid);
|
|||
|
|
l.log(`#### 【Unregister】CharacteristicValueChange: [${shortId}], remainCharacteristics: [${remainIds}]`);
|
|||
|
|
// 备注:此处不用取消特征值的侦听操作,在断开打印机的时候统一取消;
|
|||
|
|
}
|
|||
|
|
read(options) {
|
|||
|
|
return __awaiter(this, void 0, void 0, function* () {
|
|||
|
|
const deviceId = options.deviceId || "";
|
|||
|
|
const cid = options.characteristicId || "";
|
|||
|
|
const shortId = Le.getShortUUID(cid);
|
|||
|
|
const connectInfo = deviceId ? this.mConnectionMap.get(deviceId) : undefined;
|
|||
|
|
//
|
|||
|
|
if (!deviceId || !options.serviceId || !cid) {
|
|||
|
|
l.warn(`#### 【Request】readCharacteristic, 参数错误:`, options);
|
|||
|
|
return Le.complete(Ee.ERROR_PARAM, "参数错误", options);
|
|||
|
|
}
|
|||
|
|
if (!(connectInfo === null || connectInfo === void 0 ? void 0 : connectInfo.connected)) {
|
|||
|
|
l.warn(`#### 【Request】readCharacteristic, 设备连接已断开:`, options);
|
|||
|
|
return Le.complete(Ee.ERROR_DISCONNECTED, "设备连接已断开", options);
|
|||
|
|
}
|
|||
|
|
//
|
|||
|
|
return new Promise((resolve) => {
|
|||
|
|
l.info(`#### 【Request】readCharacteristic(uuid: ${shortId})`);
|
|||
|
|
// 监控特征值变化
|
|||
|
|
let resolved = false;
|
|||
|
|
if (this.mContext.isAppPlus || this.mContext.isWeiXin) {
|
|||
|
|
this.onBLECharacteristicValueChange(cid, (res) => {
|
|||
|
|
const value = typeof res.value === "string"
|
|||
|
|
? Le.hex16ToArrayBuffer(res.value)
|
|||
|
|
: new Uint8Array(res.value);
|
|||
|
|
const hexStr = typeof res.value === "string" ? res.value : Le.arrayBufferToHex16(value);
|
|||
|
|
l.info(`#### 【Response】onReadCharacteristicValueChange[${shortId}]: [${hexStr}]`);
|
|||
|
|
// 执行回调。
|
|||
|
|
if (res.characteristicId == cid) {
|
|||
|
|
resolved = true;
|
|||
|
|
Le.success(value, options, resolve);
|
|||
|
|
// 取消特征值侦听
|
|||
|
|
this.offBLECharacteristicValueChange(cid);
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
//
|
|||
|
|
uni.readBLECharacteristicValue({
|
|||
|
|
deviceId: deviceId,
|
|||
|
|
serviceId: options.serviceId,
|
|||
|
|
characteristicId: cid,
|
|||
|
|
success: (res) => {
|
|||
|
|
l.info(`#### 【Response.success】readBLECharacteristicValue: [${shortId}]:`, res);
|
|||
|
|
l.log(JSON.stringify(res));
|
|||
|
|
if (!(this.mContext.isAppPlus || this.mContext.isWeiXin)) {
|
|||
|
|
// 支付宝、钉钉、飞书等小程序直接返回;
|
|||
|
|
const result = res.characteristic || {};
|
|||
|
|
Le.success(result.value || "", options, resolve);
|
|||
|
|
}
|
|||
|
|
else {
|
|||
|
|
// AppPlus 或者 微信小程序需等待 onBLECharacteristicValueChange 来处理。
|
|||
|
|
const timeout = 1500;
|
|||
|
|
setTimeout(() => {
|
|||
|
|
if (!resolved) {
|
|||
|
|
l.warn(`#### 【Response.timeout】readBLECharacteristicValue: 响应超时, timeout: ${timeout}!`);
|
|||
|
|
Le.complete(Ee.ERROR_RESPONSE_TIMEOUT, "", options, resolve);
|
|||
|
|
}
|
|||
|
|
}, timeout);
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
fail: (res) => {
|
|||
|
|
l.warn(`#### 【Response.fail】readBLECharacteristicValue: [${shortId}]:`, res);
|
|||
|
|
Le.complete(Ee.ERROR_READ_CHARACTERISTIC, "", options, resolve);
|
|||
|
|
},
|
|||
|
|
});
|
|||
|
|
});
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
notify(options) {
|
|||
|
|
return __awaiter(this, void 0, void 0, function* () {
|
|||
|
|
const deviceId = options.deviceId || "";
|
|||
|
|
const characteristicId = options.characteristicId || "";
|
|||
|
|
const shortId = Le.getShortUUID(characteristicId);
|
|||
|
|
const connectInfo = deviceId ? this.mConnectionMap.get(deviceId) : undefined;
|
|||
|
|
if (!deviceId || !options.serviceId || !characteristicId) {
|
|||
|
|
l.warn(`#### 【Request】notifyCharacteristic, 参数错误:`, options);
|
|||
|
|
return Le.complete(Ee.ERROR_PARAM, "参数错误", options);
|
|||
|
|
}
|
|||
|
|
if (!(connectInfo === null || connectInfo === void 0 ? void 0 : connectInfo.connected)) {
|
|||
|
|
l.warn(`#### 【Request】notifyCharacteristic, 设备连接已断开:`, options);
|
|||
|
|
return Le.complete(Ee.ERROR_DISCONNECTED, "设备连接已断开", options);
|
|||
|
|
}
|
|||
|
|
//
|
|||
|
|
return new Promise((resolve) => {
|
|||
|
|
const state = typeof options.state === "boolean" ? options.state : true;
|
|||
|
|
// 取消数据监听回调,避免二次链接的时候造成的数据干扰。
|
|||
|
|
if (state) {
|
|||
|
|
l.info(`#### 【Request.start】 - notifyCharacteristic[${shortId}]:`);
|
|||
|
|
}
|
|||
|
|
else {
|
|||
|
|
this.offBLECharacteristicValueChange(characteristicId);
|
|||
|
|
l.info(`#### 【Request.stop 】 - notifyCharacteristic[${shortId}]:`);
|
|||
|
|
}
|
|||
|
|
// 为了避免遗漏数据,监听之前先注册回调;
|
|||
|
|
if (state) {
|
|||
|
|
l.info(`####【Request】onBLECharacteristicValueChange`);
|
|||
|
|
this.onBLECharacteristicValueChange(characteristicId, (result) => {
|
|||
|
|
const respVal = typeof result.value === "string"
|
|||
|
|
? Le.hex16ToArrayBuffer(result.value)
|
|||
|
|
: new Uint8Array(result.value);
|
|||
|
|
// const respId = LPAUtils.getShortUUID(result.characteristicId || "");
|
|||
|
|
// const hexStr = LPAUtils.arrayBufferToHex16(respVal);
|
|||
|
|
// logger.log(`#### 【Response】onNotifyCharacteristicValueChange[${respId}]: [${hexStr}]`, result);
|
|||
|
|
if (result.characteristicId === characteristicId) {
|
|||
|
|
options.dataReceived && options.dataReceived(respVal);
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
const res = uni.notifyBLECharacteristicValueChange({
|
|||
|
|
deviceId: deviceId,
|
|||
|
|
serviceId: options.serviceId,
|
|||
|
|
characteristicId: characteristicId,
|
|||
|
|
state: state,
|
|||
|
|
success: (res) => {
|
|||
|
|
l.info(`####【Response.success】: notifyCharacteristic[${shortId}], S[${state}]:`, res);
|
|||
|
|
if (state) {
|
|||
|
|
// 备注:在多次同时侦听流控特征值与数据特征值的时候,偶尔会接收不到数据。
|
|||
|
|
// 所以需要在特征值侦听成功后,多等待一端时间后,在执行数据的写入操作。
|
|||
|
|
setTimeout(() => {
|
|||
|
|
Le.success(res, options, resolve);
|
|||
|
|
}, 10);
|
|||
|
|
}
|
|||
|
|
else {
|
|||
|
|
Le.success(res, options, resolve);
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
fail: (res) => {
|
|||
|
|
l.warn(`#### 【Response.fail】: notifyCharacteristic, S[${state}]:`, res);
|
|||
|
|
if (state) {
|
|||
|
|
Le.complete(Ee.ERROR_NOTIFY_CHARACTERISTIC, res, options, resolve);
|
|||
|
|
}
|
|||
|
|
else {
|
|||
|
|
Le.success(res, options, resolve);
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
});
|
|||
|
|
// 钉钉小程序返回值是个 Promise,并且可能会抛异常,此处做下处理
|
|||
|
|
Promise.resolve(res).catch((error) => {
|
|||
|
|
l.warn(`#### 【Response.Exception】: notifyBLECharacteristicValueChange: `, error);
|
|||
|
|
return Le.complete(Ee.ERROR_EXCEPTION, error, options);
|
|||
|
|
});
|
|||
|
|
});
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
write(options) {
|
|||
|
|
return __awaiter(this, void 0, void 0, function* () {
|
|||
|
|
const deviceId = options.deviceId || "";
|
|||
|
|
const serviceId = options.serviceId || "";
|
|||
|
|
const characteristicId = options.characteristicId || "";
|
|||
|
|
const data = options.value || options.data;
|
|||
|
|
const writeValue = this.mContext.isAlipay ? Le.arrayBufferToHex16(data, "") : data.buffer;
|
|||
|
|
const pkgName = `[P ${options.pkgIndex} - SUP ${options.subPkgIndex}]`;
|
|||
|
|
//
|
|||
|
|
if (!deviceId || !serviceId || !characteristicId || !data) {
|
|||
|
|
l.warn(`【Request】writeCharacteristic: ${pkgName}-参数错误`, options);
|
|||
|
|
return Le.complete(Ee.ERROR_PARAM, "参数错误", options);
|
|||
|
|
}
|
|||
|
|
//
|
|||
|
|
const connectInfo = this.mConnectionMap.get(deviceId);
|
|||
|
|
if (!connectInfo || !connectInfo.connected) {
|
|||
|
|
l.warn(`####【Request】writeCharacteristic: ${pkgName}-设备连接已断开, info: `, connectInfo);
|
|||
|
|
return Le.complete(Ee.ERROR_DISCONNECTED, "设备连接已断开", options);
|
|||
|
|
}
|
|||
|
|
//
|
|||
|
|
return new Promise((resolve) => {
|
|||
|
|
if (this.mShowWriteLog) {
|
|||
|
|
l.log(`#### 【Request】writeBLECharacteristicValue: ${pkgName}`);
|
|||
|
|
}
|
|||
|
|
uni.writeBLECharacteristicValue({
|
|||
|
|
deviceId: deviceId,
|
|||
|
|
serviceId: serviceId,
|
|||
|
|
characteristicId: characteristicId,
|
|||
|
|
value: writeValue,
|
|||
|
|
// 备注:在HarmonyOS版的微信小程序中,必须要配置 writeType。
|
|||
|
|
// writeType: "writeNoResponse",
|
|||
|
|
writeType: options.withResponse ? "write" : "writeNoResponse",
|
|||
|
|
success: (res) => {
|
|||
|
|
if (this.mShowWriteLog) {
|
|||
|
|
l.log(`#### 【Response.success】writeBLECharacteristicValue: ${pkgName}`, res);
|
|||
|
|
}
|
|||
|
|
Le.success(res, options, resolve);
|
|||
|
|
// 关闭写入日志
|
|||
|
|
this.mShowWriteLog = false;
|
|||
|
|
},
|
|||
|
|
fail: (res) => {
|
|||
|
|
l.warn(`#### 【Response.fail】writeBLECharacteristicValue: ${pkgName}, writeWaits: ${options.writeWaits}`, res);
|
|||
|
|
Le.complete(Ee.ERROR_WRITE_CHARACTERISTIC, res, options, resolve);
|
|||
|
|
// 如果数据写入失败,则打开写入日志信息,供参考;
|
|||
|
|
this.mShowWriteLog = true;
|
|||
|
|
},
|
|||
|
|
});
|
|||
|
|
});
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
class LPAPIFactory {
|
|||
|
|
/**
|
|||
|
|
* 获取LPAPI接口实例。
|
|||
|
|
*/
|
|||
|
|
static getInstance(options) {
|
|||
|
|
if (!LPAPIFactory.api) {
|
|||
|
|
LPAPIFactory.api = LPAPIFactory.createInstance(options);
|
|||
|
|
}
|
|||
|
|
else if (options) {
|
|||
|
|
const prevContext = LPAPIFactory.api.drawContext;
|
|||
|
|
const context = UniContext.createInstance(options, prevContext);
|
|||
|
|
if (context && context !== prevContext) {
|
|||
|
|
LPAPIFactory.api.setDrawContext(context);
|
|||
|
|
}
|
|||
|
|
LPAPIFactory.api.setOptions(options);
|
|||
|
|
}
|
|||
|
|
//
|
|||
|
|
return LPAPIFactory.api;
|
|||
|
|
}
|
|||
|
|
static initApi(options) {
|
|||
|
|
return __awaiter(this, void 0, void 0, function* () {
|
|||
|
|
const opts = (typeof Option === "function" ? { callback: options } : options || {});
|
|||
|
|
const api = this.getInstance(options);
|
|||
|
|
//
|
|||
|
|
const res = yield api.openAdapter({ force: true });
|
|||
|
|
if (typeof opts.callback === "function") {
|
|||
|
|
opts.callback(api, res);
|
|||
|
|
}
|
|||
|
|
//
|
|||
|
|
return api;
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
static resetApi(options) {
|
|||
|
|
if (LPAPIFactory.api) {
|
|||
|
|
return LPAPIFactory.api.resetAdapter(options).then((_resp) => {
|
|||
|
|
return LPAPIFactory.getInstance(options);
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
else {
|
|||
|
|
return Promise.resolve(this.getInstance(options));
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
// /**
|
|||
|
|
// * 销毁蓝牙实例,同时关闭蓝牙适配器,释放所有已连接的资源。
|
|||
|
|
// */
|
|||
|
|
// public static quitApi() {
|
|||
|
|
// if (LPAPIFactory.api) {
|
|||
|
|
// LPAPIFactory.api.quit();
|
|||
|
|
// // 相关的 onDeviceFound 和 onCharacteristicValueChange 在UniApp中没有对应off相关事件,
|
|||
|
|
// // 如果采用多实例模式的话,相关的回调函数还是在 第一个实例中,其他实例估计无法获取回调信息。
|
|||
|
|
// // LPAPIFactory.api = undefined;
|
|||
|
|
// }
|
|||
|
|
// }
|
|||
|
|
static createInstance(options) {
|
|||
|
|
const opts = options || {};
|
|||
|
|
//
|
|||
|
|
Ri.setLogLevel(typeof opts.logLevel === "number" ? opts.logLevel : opts.showLog);
|
|||
|
|
l.info(`======== LPAPIFactory.createInstance() ========`, options);
|
|||
|
|
//
|
|||
|
|
const adapter = new BleAdapter(options);
|
|||
|
|
const context = adapter.isH5 ? undefined : UniContext.createInstance(opts);
|
|||
|
|
l.info(`---- context: `, context);
|
|||
|
|
return Ri.create(Object.assign(Object.assign({}, opts), { adapter: adapter, context: context, createContext: (args) => {
|
|||
|
|
return adapter.isH5 ? undefined : new UniContext(args);
|
|||
|
|
}, onQuit: () => {
|
|||
|
|
LPAPIFactory.api = undefined;
|
|||
|
|
} }));
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
class UniPreview extends Te {
|
|||
|
|
constructor(options) {
|
|||
|
|
super(options);
|
|||
|
|
}
|
|||
|
|
createDrawContext(options) {
|
|||
|
|
return new UniContext(Object.assign(Object.assign({}, options), { apiMode: false }));
|
|||
|
|
}
|
|||
|
|
attachTo(options) {
|
|||
|
|
if (!this.drawContext) {
|
|||
|
|
this.drawContext = this.createDrawContext(options);
|
|||
|
|
}
|
|||
|
|
// 如果未指定目标容器,则只是根据给定的容器大小来计算canvas的大小,并渲染 canvas 内容即可。
|
|||
|
|
const viewWidth = options.viewWidth || 0;
|
|||
|
|
const viewHeight = options.viewHeight || 0;
|
|||
|
|
return this.raiseViewChanged(viewWidth, viewHeight);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
function definePrintConfig(options) {
|
|||
|
|
return options;
|
|||
|
|
}
|
|||
|
|
function definePageConfig(page) {
|
|||
|
|
return page;
|
|||
|
|
}
|
|||
|
|
function defineDrawConfig(options) {
|
|||
|
|
return options;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export { a as Alignment, h as AntiColorMode, y as Barcode, Mt as Barcode1DEncoder, Re as Barcode2DEncoder, Pe as Barcode2DType, s as BarcodeTextPos, b as BarcodeType, C as BitMatrix, hi as BleConnection, c as BorderAlign, Qe as CharacteristicProperty, Ie as DegreeContent, ai as DeviceConnection, ui as DeviceManager, xe as DrawContext, ve as DrawType, p as DzCanvas, R as DzChar, P as DzCharCodes, Ge as DzCommand, be as DzImageData, Ue as DzPackage, mi as DzPrinter, Se as DzTextDecoder, I as DzTextEncoder, e as DzUtil, qe as EDataSendMode, Ze as EPrintStatus, Bt as EccLevel, me as FinderPattern, r as FontStyle, Me as GBKUtils, i as LOG_LEVEL, Ne as LPAEmitter, Ri as LPAPI, LPAPIFactory, Le as LPAUtils, Xe as LPA_DeviceConnectType, ze as LPA_DeviceType, we as LPA_GapType, Ye as LPA_GattProperty, ii as LPA_JobAction, Ae as LPA_JobName, Oe as LPA_PrintDarkness, _e as LPA_PrintSpeed, De as LPA_Printable, Ee as LPA_Result, Te as LabelContext, g as LineSpaceParser, Fe as PackageBuffer, $e as PackageBufferList, Ke as PrintPackage, Ce as QRCode, Nt as QRUtils, si as RequestMessage, o as RotateMode, f as ScaleCanvas, u as ScaleUnit, fi as SupportPrinterMatcher, n as TextDecoration, UniPreview, ti as ValueType, Pi as WdfxParser, d as WrapMode, defineDrawConfig, definePageConfig, definePrintConfig, l as logger };
|