emblemscanner: various improvements

This commit is contained in:
Fam Zheng 2025-09-16 21:51:43 +01:00
parent c8be7e4ce4
commit 5a7688cb80
3 changed files with 147 additions and 102 deletions

View File

@ -37,6 +37,7 @@ const {
// Import upload functionality for verification (self-contained)
const {
upload_image_data_urls,
upload_image_data_urls_with_metadata,
check_auto_torch
} = require('./upload.js');
@ -74,11 +75,12 @@ Page({
server_url: 'https://themblem.com', // Default server URL
real_ip: '', // User's real IP address
tenant_id: '', // Tenant identifier
debug_msgs: [],
userinfo: null, // User information from API
debug_image_data_url: '',
debug_last_result: null,
debug_current_frame_url: '', // Current frame being processed
logs: [], // Backend logging messages (like camera.js)
logs: [], // Unified logging messages for both backend reporting and debug overlay
debug_msgs: [], // Recent logs formatted for debug overlay (computed from logs)
qrtool_ready: false,
// Frame processing statistics
frames_processed: 0,
@ -129,13 +131,29 @@ Page({
this.setData({ tenant_id });
},
fetchUserInfo() {
// Use WeChat official API to get user info
wx.getUserInfo({
success: (res) => {
const userInfo = res.userInfo;
this.setData({ userinfo: userInfo });
this.log('User info fetched via API:', userInfo.nickName || 'unknown');
},
fail: (err) => {
this.log('Failed to fetch user info:', err.errMsg);
// Fallback to empty userinfo if API fails
this.setData({ userinfo: {} });
}
});
},
initializeQRTool() {
load_qrtool();
const checkReady = () => {
if (is_qrtool_ready()) {
this.setData({ qrtool_ready: true });
this.addDebugMessage('QRTool WASM loaded and ready');
this.log('QRTool WASM loaded and ready');
// Step 4: QRTool loaded, now initialize camera
this.startCameraInit();
@ -188,7 +206,7 @@ Page({
// Add WASM response to debug messages
if (this.data.enable_debug) {
this.addDebugMessage(`WASM response: ${JSON.stringify(result)}`);
this.log(`WASM response: ${JSON.stringify(result)}`);
}
// Update statistics
@ -206,7 +224,7 @@ Page({
if (result) {
// For worker, we need to trigger image collection when we find a good QR
if (result.qrcode && is_emblem_qr_pattern(result.qrcode)) {
this.addDebugMessage(`Worker QR detected: ${result.qrcode}`);
this.log(`Worker QR detected: ${result.qrcode}`);
// Check auto-torch on first valid QR detection (only when in QR detecting state)
if (this.data.app_state === 'qr_detecting') {
@ -246,19 +264,19 @@ Page({
});
if (this.image_data_urls.length >= 3) {
this.addDebugMessage('3 good images collected via worker, starting verification');
this.log('3 good images collected via worker, starting verification');
this.startVerifying();
this.submitImageForVerification(this.image_data_urls, result.qrcode);
this.image_data_urls = []; // Reset for next scan
this.setData({ ok_frames: 0 }); // Reset counter
} else {
this.addDebugMessage(`Collected ${this.image_data_urls.length}/3 worker images`);
this.log(`Collected ${this.image_data_urls.length}/3 worker images`);
}
}
}
});
this.addDebugMessage('Worker set up for iPhone processing');
this.log('Worker set up for iPhone processing');
return worker;
},
@ -271,9 +289,9 @@ Page({
if (should_use_webview) {
emblem_camera_url = "https://themblem.com/camera-5.1/?" + make_query(rule.zoom, this.data.return_page, this.data.real_ip, this.data.tenant_id);
this.addDebugMessage(`Using web-view camera: ${emblem_camera_url}`);
this.log(`Using web-view camera: ${emblem_camera_url}`);
} else {
this.addDebugMessage('Using native camera with local WASM processing');
this.log('Using native camera with local WASM processing');
}
this.setData({
@ -285,14 +303,14 @@ Page({
emblem_camera_url: emblem_camera_url
});
this.addDebugMessage(`Camera rule: zoom=${rule.zoom}, web_view=${rule.web_view}${this.data.no_web_view ? ' (NO_WEB_VIEW)' : ''}`);
this.log(`Camera rule: zoom=${rule.zoom}, web_view=${rule.web_view}${this.data.no_web_view ? ' (NO_WEB_VIEW)' : ''}`);
if (should_use_webview) {
// Step 3a: Go directly to webview scanning (no QRTool needed)
this.startWebviewScanning();
} else {
// Step 3b: Load QRTool for camera mode
this.addDebugMessage('Starting QRTool initialization');
this.log('Starting QRTool initialization');
this.transitionToState('loading_qrtool');
this.initializeQRTool();
}
@ -309,16 +327,17 @@ Page({
this.setData({
max_zoom: max_zoom
});
this.addDebugMessage(`Camera max zoom: ${max_zoom}`);
this.log(`Camera max zoom: ${max_zoom}`);
}
this.log("onCameraReady: create camera context");
this.camera_context = wx.createCameraContext();
this.addDebugMessage('Camera context created');
this.log('Camera context created');
// Step 5: Set up initial zoom and start scanning
if (this.data.camera_rule) {
this.setupCameraZoom(this.data.camera_rule);
this.addDebugMessage('Initial zoom set up');
this.log('Initial zoom set up');
}
// Step 6: Transition to scanning (which also starts frame processing)
@ -327,7 +346,7 @@ Page({
onCameraError(e) {
console.error('Camera error', e);
this.addDebugMessage(`Camera error: ${JSON.stringify(e.detail)}`);
this.log(`Camera error: ${JSON.stringify(e.detail)}`);
// Redirect with failure instead of showing modal
this.goToResult(null, null, false);
},
@ -386,7 +405,7 @@ Page({
*/
setupCameraZoom(rule) {
if (!this.camera_context) {
this.addDebugMessage('Cannot setup zoom: camera context not ready');
this.log('Cannot setup zoom: camera context not ready');
return;
}
@ -399,12 +418,12 @@ Page({
rule_zoom: zoom
});
this.addDebugMessage(`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: initial_zoom });
// Set up zoom-in behavior when QR is found
this.on_first_qr_found = () => {
this.addDebugMessage(`First QR found, zoom to ${zoom}x`);
this.log(`First QR found, zoom to ${zoom}x`);
this.camera_context.setZoom({ zoom: zoom });
this.setData({
zoom: zoom,
@ -419,7 +438,7 @@ Page({
*/
transitionToState(newState, mode = null) {
const oldState = this.data.app_state;
this.addDebugMessage(`State: ${oldState} -> ${newState}${mode ? ` (${mode})` : ''}`);
this.log(`State: ${oldState} -> ${newState}${mode ? ` (${mode})` : ''}`);
const stateData = { app_state: newState };
if (mode) stateData.scan_mode = mode;
@ -450,7 +469,7 @@ Page({
});
// Start frame processing - all conditions are implied to be ready at this point
this.addDebugMessage('Starting camera frame listener');
this.log('Starting camera frame listener');
this.lastFrameTime = 0;
// Set up camera frame listener
@ -563,7 +582,7 @@ Page({
this.handleQRResult(result, frame, uca);
}
} catch (error) {
this.addDebugMessage(`Frame processing error: ${error.message}`);
this.log(`Frame processing error: ${error.message}`);
// Still count failed processing attempts
const processEnd = Date.now();
const processingTime = processEnd - processStart;
@ -609,7 +628,7 @@ Page({
// don't require ok as we only care about the view has a valid qrcode in it
// zooming in so that it's more likely to be clear enough for upload
if (result.qrcode && is_emblem_qr_pattern(result.qrcode)) {
this.addDebugMessage(`QR detected: ${result.qrcode} ok: ${result.ok}: err ${result.err}`);
this.log(`QR detected: ${result.qrcode} ok: ${result.ok}: err ${result.err}`);
// Check auto-torch on first valid QR detection (only when in QR detecting state)
if (this.data.app_state === 'qr_detecting') {
@ -631,7 +650,7 @@ Page({
this.setData({ hint_text: hint });
if (result.qrcode) {
this.addDebugMessage(`QR found but not valid: ${result.qrcode} (${result.err})`);
this.log(`QR found but not valid: ${result.qrcode} (${result.err})`);
}
}
},
@ -652,16 +671,16 @@ Page({
ok_frames: this.image_data_urls.length
});
this.addDebugMessage(`Collected ${this.image_data_urls.length}/3 good images (direct) - using copied data`);
this.log(`Collected ${this.image_data_urls.length}/3 good images (direct) - using copied data`);
// Add debug info about the submitted image
if (this.data.enable_debug) {
this.addDebugMessage(`Submitted image preview: ${dataUrl.substring(0, 50)}...`);
this.log(`Submitted image preview: ${dataUrl.substring(0, 50)}...`);
}
// Need 3 "ok" frames before verification (like camera.js)
if (this.image_data_urls.length >= 3) {
this.addDebugMessage('3 good images collected, starting verification');
this.log('3 good images collected, starting verification');
this.startVerifying();
this.submitImageForVerification(this.image_data_urls, qrCode);
this.image_data_urls = []; // Reset for next scan
@ -676,11 +695,11 @@ Page({
* Submit images for verification like camera.js
*/
submitImageForVerification(dataUrls, qrCode) {
this.addDebugMessage('Submitting images for verification');
this.log('Submitting images for verification');
const begin = Date.now();
const success = (res) => {
this.addDebugMessage(`Upload success, code: ${res.statusCode}`);
this.log(`Upload success, code: ${res.statusCode}`);
if (res.statusCode === 200) {
let resp;
if (typeof res.data === "string") {
@ -712,7 +731,7 @@ Page({
};
const fail = (e) => {
this.addDebugMessage(`Upload failed: ${JSON.stringify(e)}`);
this.log(`Upload failed: ${JSON.stringify(e)}`);
// Redirect with failure instead of showing modal
this.goToResult(qrCode, null, false);
};
@ -722,7 +741,15 @@ Page({
gd.image_data_urls = dataUrls;
gd.qr_code = qrCode;
upload_image_data_urls(dataUrls, success, fail, this.data.logs.join("\n"));
// Use enhanced upload function with local metadata instead of global data
const metadata = {
real_ip: this.data.real_ip,
phone_model: this.data.phone_model,
server_url: this.data.server_url,
userinfo: this.data.userinfo
};
upload_image_data_urls_with_metadata(dataUrls, qrCode, metadata, success, fail, this.data.logs.join("\n"));
},
@ -747,13 +774,13 @@ Page({
});
check_auto_torch(qrcode, (auto_torch, camera_sensitivity) => {
this.addDebugMessage(`Auto-torch check: ${auto_torch}, sensitivity: ${camera_sensitivity}`);
this.log(`Auto-torch check: ${auto_torch}, sensitivity: ${camera_sensitivity}`);
if (auto_torch) {
this.setData({
camera_flash: 'torch'
});
this.addDebugMessage('Auto-torch enabled');
this.log('Auto-torch enabled');
}
// Update camera sensitivity and continue scanning
@ -811,10 +838,10 @@ Page({
wx.redirectTo({
url: url,
success: () => {
this.addDebugMessage(`Navigated to: ${this.data.return_page}`);
this.log(`Navigated to: ${this.data.return_page}`);
},
fail: (err) => {
this.addDebugMessage(`Navigation failed: ${err.errMsg}`);
this.log(`Navigation failed: ${err.errMsg}`);
this.restart_camera();
}
});
@ -852,30 +879,31 @@ Page({
},
/**
* Log function for backend reporting (like camera.js)
* Unified logging function for both backend reporting and debug overlay
*/
log(...what) {
const message = what.join(" ");
// Always log to console
console.log(...what);
this.setData({
logs: this.data.logs.concat([new Date() + ": " + what.join(" ")])
});
},
/**
* Add debug message to debug overlay and backend logs
*/
addDebugMessage(message) {
// Always add to backend logs for reporting
this.log(message);
// Only show in debug overlay if debug is enabled
if (!this.data.enable_debug) return;
const newLogs = this.data.logs.concat([new Date() + ": " + message]);
// Update debug overlay with recent logs if debug is enabled
let debugMsgs = [];
if (this.data.enable_debug) {
// Show only the 5 most recent logs, formatted with short timestamp
debugMsgs = newLogs.slice(-5).reverse().map(log => {
const timestamp = new Date().toLocaleTimeString();
const debugMsg = `${timestamp}: ${message}`;
const logMessage = log.substring(log.indexOf(": ") + 2); // Remove full date, keep message
return `${timestamp}: ${logMessage}`;
});
}
this.setData({
debug_msgs: [debugMsg, ...this.data.debug_msgs].slice(0, 5) // Keep first 5 messages (newest on top)
logs: newLogs,
debug_msgs: debugMsgs
});
},
@ -891,20 +919,12 @@ Page({
});
},
/**
* Log function for debugging
*/
log(...args) {
console.log(...args);
this.addDebugMessage(args.join(' '));
},
/**
* Handle messages from web-view camera
*/
on_webview_message(e) {
console.log('Web-view message received:', e);
this.addDebugMessage(`Web-view message: ${JSON.stringify(e.detail)}`);
this.log(`Web-view message: ${JSON.stringify(e.detail)}`);
// Handle QR code results from web-view
if (e.detail && e.detail.data && e.detail.data.length > 0) {
@ -913,7 +933,7 @@ Page({
// Web-view results go directly to result (no verification step)
this.goToResult(messageData.qr_code, messageData.serial_code, true);
} else if (messageData.error) {
this.addDebugMessage(`Web-view error: ${messageData.error}`);
this.log(`Web-view error: ${messageData.error}`);
// Redirect with failure instead of showing hint
this.goToResult(null, null, false);
}
@ -951,7 +971,12 @@ Page({
ok_frames: 0,
total_processing_time: 0,
avg_processing_time_ms: 0,
last_frame_time_ms: 0
last_frame_time_ms: 0,
// Clear logs and debug messages
logs: [],
debug_msgs: [],
// Clear userinfo
userinfo: null
});
// Initialize image collection for verification
@ -963,6 +988,7 @@ Page({
this.initializeSystem(enable_debug);
this.fetchRealIP();
this.fetchTenantID();
this.fetchUserInfo();
// Step 2: Load camera rules based on phone model
this.loadCameraRules();
@ -987,7 +1013,10 @@ Page({
this.image_data_urls = [];
// Clear logs to prevent memory buildup
this.setData({ logs: [] });
this.setData({
logs: [],
debug_msgs: []
});
// Clear zoom function reference
this.on_first_qr_found = null;
@ -1005,15 +1034,16 @@ Page({
*/
cleanupListener() {
if (this.listener) {
this.log("cleanupListener: stop camera frame listener");
this.listener.stop();
this.listener = null;
this.addDebugMessage('Camera frame listener stopped');
this.log('Camera frame listener stopped');
}
// Clear camera context to prevent multiple camera elements
if (this.camera_context) {
this.camera_context = null;
this.addDebugMessage('Camera context cleared');
this.log('Camera context cleared');
}
// Clean up worker state
@ -1041,7 +1071,7 @@ Page({
// Clear stored worker frame reference
this.lastWorkerFrame = null;
this.addDebugMessage('Worker state cleaned up');
this.log('Worker state cleaned up');
}
}

View File

@ -11,51 +11,34 @@
<text>初始化QR工具...</text>
</view>
<!-- STATE: INIT_CAMERA -->
<block wx:if="{{ app_state == 'init_camera' }}">
<!-- Show camera without overlays during initialization -->
<view class="osd">
<view class="upper" bindtap="debug_tap">
{{ hint_text }}
</view>
</view>
<!-- CAMERA STATES: INIT_CAMERA, QR_DETECTING, CHECK_AUTO_TORCH, or FINAL_SCANNING -->
<block wx:if="{{ app_state == 'init_camera' || app_state == 'qr_detecting' || app_state == 'check_auto_torch' || app_state == 'final_scanning' }}">
<!-- WeChat native camera -->
<camera class="camera"
flash="{{ camera_flash }}"
frame-size="large"
resolution="high"
bindinitdone="onCameraReady"
binderror="onCameraError">
</camera>
</block>
<!-- STATE: QR_DETECTING, CHECK_AUTO_TORCH, or FINAL_SCANNING -->
<block wx:if="{{ app_state == 'qr_detecting' || app_state == 'check_auto_torch' || app_state == 'final_scanning' }}">
<!-- QR targeting arcs overlay -->
<view class="qrarc {{ qrarc_class }}">
<!-- QR targeting arcs overlay (only show during scanning states) -->
<view wx:if="{{ app_state == 'qr_detecting' || app_state == 'check_auto_torch' || app_state == 'final_scanning' }}" class="qrarc {{ qrarc_class }}">
<image class="topleft arc" src="./assets/arc.png"></image>
<image class="topright arc" src="./assets/arc.png"></image>
<image class="bottomleft arc" src="./assets/arc.png"></image>
<image class="bottomright arc" src="./assets/arc.png"></image>
</view>
<!-- QR markers overlay -->
<view class="qrmarkers {{ qrmarkers_class }}">
<!-- QR markers overlay (only show during scanning states) -->
<view wx:if="{{ app_state == 'qr_detecting' || app_state == 'check_auto_torch' || app_state == 'final_scanning' }}" class="qrmarkers {{ qrmarkers_class }}">
<image class="square" src="./assets/qrmarkers.png"></image>
</view>
<!-- On-screen display for hints -->
<!-- On-screen display for hints (all camera states) -->
<view class="osd">
<view class="upper" bindtap="debug_tap">
{{ hint_text }}
</view>
</view>
<!-- WeChat native camera -->
<!-- Single WeChat native camera for all camera states -->
<camera class="camera"
flash="{{ camera_flash }}"
frame-size="medium"
frame-size="{{ app_state == 'init_camera' ? 'large' : 'medium' }}"
resolution="{{ app_state == 'init_camera' ? 'high' : 'medium' }}"
bindinitdone="onCameraReady"
binderror="onCameraError">
</camera>

View File

@ -61,7 +61,39 @@ function upload_image_data_urls(image_data_urls, success, fail, log) {
});
}
function upload_image_data_urls_with_metadata(image_data_urls, qrcode, metadata, success, fail, log) {
var gd = getApp().globalData;
var fd = {
emblem_id: metadata.userinfo.emblem_id,
nick_name: metadata.userinfo.nickName,
realip: metadata.real_ip,
qrcode: qrcode,
angle: 0,
phonemodel: metadata.phone_model,
image_data_urls,
use_roi_verify: 1,
log,
};
var ci = gd.caller_info;
if (ci && ci.token) {
fd.token = ci.token;
}
var url = metadata.server_url + '/api/v1/qr-verify/';
console.log("wx.request", url, fd.qrcode, fd.angle, fd.phonemodel, fd.realip);
wx.request({
url,
method: "POST",
header: {
"Content-Type": "application/json",
},
data: JSON.stringify(fd),
success,
fail,
});
}
module.exports = {
upload_image_data_urls,
upload_image_data_urls_with_metadata,
check_auto_torch,
};