emblemscanner: subpackage support

This commit is contained in:
Fam Zheng 2025-10-15 11:09:48 +01:00
parent fffc50372e
commit e68348fbff
9 changed files with 301 additions and 26 deletions

1
.gitignore vendored
View File

@ -5,3 +5,4 @@ build
/dataset/local /dataset/local
/detection/model /detection/model
/api/db.sqlite3 /api/db.sqlite3
/emblemscanner-release

View File

@ -135,8 +135,8 @@ RELEASE_VERSION := $(shell git describe --tags --abbrev=0 | sed 's/^emblemscanne
emblemscanner-release: build/emblemscanner-$(RELEASE_VERSION).tar.gz emblemscanner-release: build/emblemscanner-$(RELEASE_VERSION).tar.gz
build/emblemscanner-$(RELEASE_VERSION).tar.gz: build/emblemscanner-$(RELEASE_VERSION).tar.gz: FORCE
# if tree dirty or git head not tagged, error out - rm -rf $@
git diff --exit-code git diff --exit-code
if test -z "$(RELEASE_VERSION)"; then \ if test -z "$(RELEASE_VERSION)"; then \
echo "RELEASE_VERSION is empty"; \ echo "RELEASE_VERSION is empty"; \
@ -146,4 +146,6 @@ build/emblemscanner-$(RELEASE_VERSION).tar.gz:
echo "git head not tagged as $(RELEASE_VERSION)"; \ echo "git head not tagged as $(RELEASE_VERSION)"; \
exit 1; \ exit 1; \
fi fi
tar -czvf build/emblemscanner-$(RELEASE_VERSION).tar.gz -C scanner pages/emblemscanner tar -cf build/emblemscanner-$(RELEASE_VERSION).tar -C scanner pages/emblemscanner
cd scanner/pages/emblemscanner && tar -uf ../../../build/emblemscanner-$(RELEASE_VERSION).tar README.md
gzip build/emblemscanner-$(RELEASE_VERSION).tar

View File

@ -7,7 +7,23 @@ var performance = {
}; };
Module['instantiateWasm'] = (info, receiveInstance) => { Module['instantiateWasm'] = (info, receiveInstance) => {
console.log("loading wasm...", info); console.log("loading wasm...", info);
WebAssembly.instantiate("pages/emblemscanner/qrtool.wx.wasm.br", info).then((result) => {
// Use dynamic path from global data if available, otherwise fallback to hardcoded path
var wasmPath = "pages/emblemscanner/qrtool.wx.wasm.br";
try {
// Check if we're in a WeChat miniprogram environment and have access to getApp
if (typeof getApp !== 'undefined') {
var app = getApp();
if (app && app.globalData && app.globalData.wasmFilePath) {
wasmPath = app.globalData.wasmFilePath;
console.log("Using dynamic WASM path:", wasmPath);
}
}
} catch (e) {
console.warn("Failed to get dynamic WASM path, using fallback:", e);
}
WebAssembly.instantiate(wasmPath, info).then((result) => {
console.log("result:", result); console.log("result:", result);
var inst = result['instance']; var inst = result['instance'];
receiveInstance(inst); receiveInstance(inst);

View File

@ -0,0 +1,104 @@
# EmblemScanner 微信小程序扫码验证模块
## Release notes
- Version 1.0.1: initial release
- Version 1.1.0: sub package support
## 概述
EmblemScanner 是一个专为微信小程序设计的二维码扫描验证模块,集成了先进的 AI 图像识别技术和二维码检测算法,能够快速准确地识别和验证二维码。
## 集成方式和接口
### 调用参数
| 参数 | 说明 | 示例值 |
|------|------|--------|
| return_page | 扫描完成后跳转的页面路径 | `/pages/scan_result/scan_result` |
### 扫描结果
扫描成功后,模块会跳转到指定的 return_page携带以下参数
- `ok=1` - 扫描成功
- `qr_code` - 二维码内容
- `serial_code` - 验证序列号(如有)
扫描失败时:
- `ok=0` - 扫描失败
### 依赖要求
- 相机权限
- 网络访问权限(连接 themblem.com API等
## 使用步骤
### 1. 项目准备
创建 JS 版小程序项目。
### 2. 文件部署
解压 emblemscanner 代码到小程序项目目录。
```
pages/emblemscanner/
├── assets/ # 静态资源
├── emblemscanner.json # 页面配置
├── emblemscanner.wxml # 页面结构
├── emblemscanner.wxss # 样式文件
├── emblemscanner.js # 主页面逻辑
├── libemblemscanner.js # 核心库
├── qrprocessor.js # 二维码处理器
├── qrtool.wx.js # 二维码工具
├── qrtool.wx.wasm.br # WebAssembly 二进制
├── upload.js # 上传模块
└── worker/ # Worker 线程
```
### 3. 项目配置
`app.json` 中添加页面和 worker 配置:
```json
{
"pages": [
"pages/index/index",
"pages/emblemscanner/emblemscanner",
"pages/logs/logs"
],
"workers": "pages/emblemscanner/worker"
}
```
### 4. 域名配置
参考[微信小程序网络配置指南](https://developers.weixin.qq.com/miniprogram/dev/framework/ability/network.html),在小程序后台配置合法域名:
- `themblem.com`
### 5. 扫码调用
#### 跳转到扫码页面:
```javascript
navigateToScanner() {
wx.navigateTo({
url: '/pages/emblemscanner/emblemscanner?return_page=/pages/scan_result/scan_result'
})
}
```
#### 处理扫码结果:
```javascript
onLoad(options) {
var verify_ok = options['ok']; // ok == 1表示验证通过
var qr_code = options['qr_code']; // 二维码内容
var serial_code = options['serial_code']; // 序列码
this.setData({
verify_ok,
qr_code,
serial_code,
})
}
```

View File

@ -1,6 +1,12 @@
// QR Scanner Module - Self-contained QR scanning page // QR Scanner Module - Self-contained QR scanning page
// Adapted from existing camera implementation // Adapted from existing camera implementation
// //
// DYNAMIC PATH RESOLUTION:
// - The system now dynamically computes the correct WASM file and worker paths based on the current page location
// - This allows the emblemscanner to work in both main pages directory and subpackages
// - Paths are computed using getCurrentPages() API and stored in global data during page load
// - WASM files and workers use these dynamic paths instead of hardcoded absolute paths
//
// SCANNING MODES: // SCANNING MODES:
// 1. Web View Mode - Uses external camera-5.1 web interface in web-view component // 1. Web View Mode - Uses external camera-5.1 web interface in web-view component
// - Fallback for devices that need special handling // - Fallback for devices that need special handling
@ -31,7 +37,9 @@ const {
make_query, make_query,
fetch_real_ip, fetch_real_ip,
get_tenant_id, get_tenant_id,
is_emblem_qr_pattern is_emblem_qr_pattern,
get_current_package_path,
get_wasm_file_path
} = require('./libemblemscanner.js'); } = require('./libemblemscanner.js');
// Import upload functionality for verification (self-contained) // Import upload functionality for verification (self-contained)
@ -115,6 +123,13 @@ Page({
no_web_view: no_web_view no_web_view: no_web_view
}); });
// Store current package path in global data for dynamic WASM loading
const currentPackagePath = get_current_package_path();
getApp().globalData.currentPackagePath = currentPackagePath;
getApp().globalData.wasmFilePath = get_wasm_file_path('qrtool.wx.wasm.br');
console.log('Current package path:', currentPackagePath);
console.log('WASM file path:', getApp().globalData.wasmFilePath);
// Log page load for backend reporting (like camera.js) // Log page load for backend reporting (like camera.js)
this.log("emblemscanner page load"); this.log("emblemscanner page load");
this.log("options", JSON.stringify(options)); this.log("options", JSON.stringify(options));
@ -189,8 +204,12 @@ Page({
* Set up worker for iPhone processing like camera.js * Set up worker for iPhone processing like camera.js
*/ */
setupWorker() { setupWorker() {
// Create worker with local worker file // Create worker with dynamic path based on current package location
var worker = wx.createWorker('/pages/emblemscanner/worker/index.js', { var packagePath = getApp().globalData.currentPackagePath || 'pages/emblemscanner';
var workerPath = `${packagePath}/worker/index.js`;
console.log('Creating worker with path:', workerPath);
var worker = wx.createWorker(workerPath, {
useExperimentalWorker: true, useExperimentalWorker: true,
}); });
@ -240,7 +259,10 @@ Page({
if (this.data.app_state === 'final_scanning' && result.ok) { if (this.data.app_state === 'final_scanning' && result.ok) {
// Request worker to submit image data // Request worker to submit image data
this.get_worker().postMessage({ type: "ready_to_submit" }); const worker = this.get_worker();
if (worker) {
worker.postMessage({ type: "ready_to_submit" });
}
} }
} }
@ -361,7 +383,6 @@ Page({
show_scanguide() { show_scanguide() {
console.log('show_scanguide');
this.setData({ this.setData({
show_modal: 'scanguide', show_modal: 'scanguide',
show_tip: false show_tip: false
@ -420,12 +441,12 @@ Page({
}); });
this.log(`Camera set initial zoom to ${initial_zoom}x, will zoom in to ${zoom}x when QR is found`); this.log(`Camera set initial zoom to ${initial_zoom}x, will zoom in to ${zoom}x when QR is found`);
this.camera_context.setZoom({ zoom: 6 }); this.camera_context.setZoom({ zoom: initial_zoom });
// Set up zoom-in behavior when QR is found // Set up zoom-in behavior when QR is found
this.on_first_qr_found = () => { this.on_first_qr_found = () => {
this.log(`First QR found, zoom to ${zoom}x`); this.log(`First QR found, zoom to ${zoom}x`);
this.camera_context.setZoom({ zoom: 6 }); this.camera_context.setZoom({ zoom: zoom });
this.setData({ this.setData({
zoom: zoom, zoom: zoom,
qrmarkers_class: '', qrmarkers_class: '',
@ -479,7 +500,7 @@ Page({
}); });
// Start the listener with worker if using worker mode // Start the listener with worker if using worker mode
var worker = this.get_worker(); var worker = this.data.use_worker ? this.get_worker() : null;
this.listener.start({ this.listener.start({
worker, worker,
}); });
@ -531,12 +552,15 @@ Page({
// Set processing flag before sending message // Set processing flag before sending message
this.setData({ worker_processing: true }); this.setData({ worker_processing: true });
this.get_worker().postMessage({ const worker = this.get_worker();
if (worker) {
worker.postMessage({
type: 'frame', type: 'frame',
width: frame.width, width: frame.width,
height: frame.height, height: frame.height,
camera_sensitivity: this.data.camera_sensitivity camera_sensitivity: this.data.camera_sensitivity
}); });
}
} else { } else {
// Direct processing (other devices) // Direct processing (other devices)
this.processFrameDirect(frame); this.processFrameDirect(frame);
@ -698,7 +722,6 @@ Page({
submitImageForVerification(dataUrls, qrCode) { submitImageForVerification(dataUrls, qrCode) {
this.log('Submitting images for verification'); this.log('Submitting images for verification');
const begin = Date.now(); const begin = Date.now();
wx.vibrateShort({ type: "heavy" });
const success = (res) => { const success = (res) => {
this.log(`Upload success, code: ${res.statusCode}`); this.log(`Upload success, code: ${res.statusCode}`);
@ -1029,6 +1052,7 @@ Page({
*/ */
onUnload() { onUnload() {
this.cleanupListener(); this.cleanupListener();
this.terminateWorker();
}, },
/** /**
@ -1053,11 +1077,10 @@ Page({
}, },
get_worker() { get_worker() {
var gd = getApp().globalData; if (!this.worker && this.data.use_worker) {
if (!gd.emblemscanner_worker) { this.worker = this.setupWorker();
gd.emblemscanner_worker = this.setupWorker();
} }
return gd.emblemscanner_worker; return this.worker;
}, },
/** /**
@ -1075,6 +1098,17 @@ Page({
this.log('Worker state cleaned up'); this.log('Worker state cleaned up');
} }
},
/**
* Terminate and clean up worker on page unload
*/
terminateWorker() {
if (this.worker) {
this.worker.terminate();
this.worker = null;
this.log('Worker terminated');
}
} }
}); });

View File

@ -1,3 +1,13 @@
page {
width: 100%;
height: 100%;
}
.hidden {
display:none;
}
/* Main container */ /* Main container */
view.wrapper { view.wrapper {
width: 100%; width: 100%;

View File

@ -131,6 +131,83 @@ function is_emblem_qr_pattern(p) {
return false; return false;
} }
/**
* Get the current package path for dynamic WASM file loading
* This function determines the correct path to WASM files based on the current page location
*/
function get_current_package_path() {
try {
// Get current pages stack to determine current page location
const pages = getCurrentPages();
if (pages && pages.length > 0) {
const currentPage = pages[pages.length - 1];
const route = currentPage.route;
// Extract package path from route
// For example: "bofen/packages/emblemscanner/emblemscanner" -> "bofen/packages/emblemscanner"
const pathParts = route.split('/');
if (pathParts.length >= 2) {
// Remove the page file name and return the package path
return pathParts.slice(0, -1).join('/');
}
}
} catch (e) {
console.warn('Failed to get current package path:', e);
}
// Fallback: assume we're in the main pages directory
return 'pages/emblemscanner';
}
/**
* Get the full WASM file path based on current package location
*/
function get_wasm_file_path(filename) {
const packagePath = get_current_package_path();
return `${packagePath}/${filename}`;
}
/**
* Test function to verify path resolution works correctly
* This can be called in development to test the path resolution logic
*/
function test_path_resolution() {
console.log('Testing path resolution...');
// Mock getCurrentPages for testing (would normally be available in WeChat miniprogram)
const originalGetCurrentPages = global.getCurrentPages;
global.getCurrentPages = function() {
return [{
route: 'bofen/packages/emblemscanner/emblemscanner'
}];
};
try {
const packagePath = get_current_package_path();
const wasmPath = get_wasm_file_path('qrtool.wx.wasm.br');
console.log('Expected package path: bofen/packages/emblemscanner');
console.log('Actual package path:', packagePath);
console.log('Expected WASM path: bofen/packages/emblemscanner/qrtool.wx.wasm.br');
console.log('Actual WASM path:', wasmPath);
if (packagePath === 'bofen/packages/emblemscanner' && wasmPath === 'bofen/packages/emblemscanner/qrtool.wx.wasm.br') {
console.log('✅ Path resolution test PASSED');
return true;
} else {
console.log('❌ Path resolution test FAILED');
return false;
}
} finally {
// Restore original function
if (originalGetCurrentPages) {
global.getCurrentPages = originalGetCurrentPages;
} else {
delete global.getCurrentPages;
}
}
}
module.exports = { module.exports = {
get_system_info, get_system_info,
get_phone_model, get_phone_model,
@ -138,5 +215,8 @@ module.exports = {
make_query, make_query,
fetch_real_ip, fetch_real_ip,
get_tenant_id, get_tenant_id,
is_emblem_qr_pattern is_emblem_qr_pattern,
get_current_package_path,
get_wasm_file_path,
test_path_resolution
}; };

View File

@ -34,7 +34,21 @@ var performance = {
}; };
Module["instantiateWasm"] = (info, receiveInstance) => { Module["instantiateWasm"] = (info, receiveInstance) => {
console.log("loading wasm...", info); console.log("loading wasm...", info);
WebAssembly.instantiate("pages/emblemscanner/qrtool.wx.wasm.br", info).then(result => { // Use dynamic path from global data if available, otherwise fallback to hardcoded path
var wasmPath = "pages/emblemscanner/qrtool.wx.wasm.br";
try {
// Check if we're in a WeChat miniprogram environment and have access to getApp
if (typeof getApp !== "undefined") {
var app = getApp();
if (app && app.globalData && app.globalData.wasmFilePath) {
wasmPath = app.globalData.wasmFilePath;
console.log("Using dynamic WASM path:", wasmPath);
}
}
} catch (e) {
console.warn("Failed to get dynamic WASM path, using fallback:", e);
}
WebAssembly.instantiate(wasmPath, info).then(result => {
console.log("result:", result); console.log("result:", result);
var inst = result["instance"]; var inst = result["instance"];
receiveInstance(inst); receiveInstance(inst);

View File

@ -34,7 +34,21 @@ var performance = {
}; };
Module["instantiateWasm"] = (info, receiveInstance) => { Module["instantiateWasm"] = (info, receiveInstance) => {
console.log("loading wasm...", info); console.log("loading wasm...", info);
WebAssembly.instantiate("pages/emblemscanner/qrtool.wx.wasm.br", info).then(result => { // Use dynamic path from global data if available, otherwise fallback to hardcoded path
var wasmPath = "pages/emblemscanner/qrtool.wx.wasm.br";
try {
// Check if we're in a WeChat miniprogram environment and have access to getApp
if (typeof getApp !== "undefined") {
var app = getApp();
if (app && app.globalData && app.globalData.wasmFilePath) {
wasmPath = app.globalData.wasmFilePath;
console.log("Using dynamic WASM path:", wasmPath);
}
}
} catch (e) {
console.warn("Failed to get dynamic WASM path, using fallback:", e);
}
WebAssembly.instantiate(wasmPath, info).then(result => {
console.log("result:", result); console.log("result:", result);
var inst = result["instance"]; var inst = result["instance"];
receiveInstance(inst); receiveInstance(inst);