485 lines
13 KiB
JavaScript
485 lines
13 KiB
JavaScript
// pages/camera.js
|
|
//
|
|
import {
|
|
upload_image_data_url,
|
|
check_auto_torch,
|
|
} from '../../upload.js'
|
|
|
|
import {
|
|
frame_pre_check,
|
|
data_url_from_frame,
|
|
load_qrtool,
|
|
is_emblem_qr_pattern,
|
|
} from '../../precheck.js'
|
|
|
|
import {
|
|
get_camera_rule,
|
|
get_system_info,
|
|
} from '../../utils.js'
|
|
|
|
import { debug, info, error } from '../../log.js'
|
|
|
|
Page({
|
|
|
|
/**
|
|
* Page initial data
|
|
*/
|
|
data: {
|
|
hint_text: '查找二维码',
|
|
enable_debug: false,
|
|
debug_countdown: 10,
|
|
camera_flash: "off",
|
|
camoverlay_grayscale: 0,
|
|
phone_model: "unknown",
|
|
progress: 0,
|
|
zoom: -1,
|
|
rule_zoom: -1,
|
|
max_zoom: 1,
|
|
use_worker: false,
|
|
show_tip: false,
|
|
show_modal: '',
|
|
busy: true,
|
|
logs: [],
|
|
should_check_auto_torch: true,
|
|
done_checking_auto_torch: false,
|
|
camera_sensitivity: 1,
|
|
frame_uploaded: 0,
|
|
last_frame_upload_time: 0,
|
|
frame_upload_seq_num: 0,
|
|
qrarc_class: "sm",
|
|
qrmarkers_class: "hidden",
|
|
frame_upload_in_flight: 0,
|
|
frame_upload_time_cost: 0,
|
|
frame_upload_interval_ms: 2000,
|
|
},
|
|
|
|
make_hint_text(r) {
|
|
var qr_is_valid = false;
|
|
if (r.qrcode && r.qrcode.length > 0) {
|
|
qr_is_valid = is_emblem_qr_pattern(r.qrcode);
|
|
if (!qr_is_valid) {
|
|
return "无效编码";
|
|
}
|
|
}
|
|
if (qr_is_valid) {
|
|
if (this.on_qr_found) {
|
|
this.on_qr_found();
|
|
this.on_qr_found = null;
|
|
}
|
|
var err = r.err || "";
|
|
if (err.includes("margin too small")) {
|
|
return "对齐定位点";
|
|
} else if (err.includes("energy check failed") || err.includes("cannot detect angle")) {
|
|
return "移近一点";
|
|
}
|
|
return "对齐定位点";
|
|
}
|
|
return "查找二维码";
|
|
},
|
|
|
|
log(...what) {
|
|
console.log(...what);
|
|
this.setData({
|
|
logs: this.data.logs.concat([new Date() + ": " + what.join(" ")])
|
|
});
|
|
},
|
|
|
|
onLoad(options) {
|
|
var new_session_id = Date.now();
|
|
getApp().globalData.session_id = new_session_id;
|
|
if (options.env == 'dev') {
|
|
console.log("Using dev env settings.");
|
|
getApp().globalData.server_url = 'https://dev.themblem.com';
|
|
}
|
|
this.log("camera page load (build240622.2120)");
|
|
get_camera_rule(null, () => {});
|
|
this.log("options", options);
|
|
options = options || {};
|
|
if (options.debug || options.scene == 'debug') {
|
|
getApp().globalData.debug = true
|
|
}
|
|
var enable_debug = getApp().globalData.debug;
|
|
const si = get_system_info();
|
|
const phone_model = si.model;
|
|
this.log("phone model: " + phone_model);
|
|
getApp().globalData.phone_model = phone_model;
|
|
this.log("window width", si.windowWidth, "height", si.windowHeight);
|
|
const use_worker = phone_model.toLowerCase().includes("iphone");
|
|
this.setData({
|
|
enable_debug,
|
|
phone_model,
|
|
window_width: si.windowWidth,
|
|
window_height: si.windowHeight,
|
|
use_worker,
|
|
});
|
|
if (use_worker) {
|
|
this.worker = getApp().globalData.worker;
|
|
this.worker.onMessage((msg) => {
|
|
if (msg.type == "result") {
|
|
var res = msg.res;
|
|
this.setData({
|
|
processing_time: Date.now() - this.processing_begin,
|
|
result: JSON.stringify(res),
|
|
});
|
|
if (this.data.should_check_auto_torch && is_emblem_qr_pattern(res.qrcode)) {
|
|
this.start_check_auto_torch(res.qrcode);
|
|
}
|
|
if (this.data.done_checking_auto_torch && res.ok && res.qrcode.length && is_emblem_qr_pattern(res.qrcode)) {
|
|
this.pending_hint_text = null;
|
|
this.setData({
|
|
hint_text: "识别成功",
|
|
show_modal: "verifying",
|
|
});
|
|
this.fetch_from_worker();
|
|
} else {
|
|
this.pending_hint_text = this.make_hint_text(res);
|
|
this.reset_busy();
|
|
}
|
|
} else if (this.data.done_checking_auto_torch && msg.type == "submit") {
|
|
this.log("got submit");
|
|
var res = msg.res;
|
|
const image_data = msg.image_data;
|
|
var uca = new Uint8ClampedArray(image_data.data);
|
|
var data_url = data_url_from_frame(image_data.width, image_data.height, uca);
|
|
this.submit_image(data_url, res.qrcode, res.angle);
|
|
}
|
|
});
|
|
} else {
|
|
load_qrtool();
|
|
}
|
|
},
|
|
start_check_auto_torch(qrcode) {
|
|
this.setData({
|
|
should_check_auto_torch: false,
|
|
});
|
|
check_auto_torch(qrcode, (auto_torch, camera_sensitivity) => {
|
|
this.log("check_auto_torch cb", auto_torch);
|
|
if (auto_torch) {
|
|
this.torch_on();
|
|
}
|
|
setTimeout(() => {
|
|
this.setData({
|
|
done_checking_auto_torch: true,
|
|
camera_sensitivity: camera_sensitivity || 1,
|
|
});
|
|
}, 300);
|
|
}
|
|
);
|
|
},
|
|
|
|
fetch_from_worker() {
|
|
// send the signal message to worker to fetch the image data
|
|
this.log("fetch_from_worker");
|
|
this.worker.postMessage({
|
|
type: "ready_to_submit",
|
|
});
|
|
},
|
|
|
|
onShow() {
|
|
this.log("camera page show");
|
|
this.setup_camera();
|
|
this.setData({
|
|
show_tip: false,
|
|
})
|
|
this.reset_busy();
|
|
this.hint_interval = setInterval(() => {
|
|
this.update_hint_text();
|
|
}, 1000);
|
|
this.setup_tooltip_timer();
|
|
},
|
|
|
|
update_hint_text() {
|
|
if (this.pending_hint_text) {
|
|
this.setData({
|
|
hint_text: this.pending_hint_text,
|
|
});
|
|
this.pending_hint_text = null;
|
|
}
|
|
},
|
|
|
|
setup_tooltip_timer() {
|
|
if (this.tooltip_timer) {
|
|
clearTimeout(this.tooltip_timer);
|
|
this.tooltip_timer = null;
|
|
}
|
|
this.tooltip_timer = setTimeout(() => {
|
|
if (this.data.show_modal != '') return;
|
|
wx.vibrateShort({ type: "heavy", });
|
|
this.setData({
|
|
show_tip: true,
|
|
});
|
|
}, 15000);
|
|
},
|
|
|
|
clean_up() {
|
|
clearTimeout(this.tooltip_timer);
|
|
clearTimeout(this.hint_interval);
|
|
this.listener.stop();
|
|
this.listener = null;
|
|
},
|
|
|
|
onHide() {
|
|
this.busy = true;
|
|
this.clean_up();
|
|
},
|
|
|
|
onUnload() {
|
|
this.clean_up();
|
|
},
|
|
|
|
submit_image(data_url, qr_code, angle) {
|
|
this.log("submit image", qr_code, angle);
|
|
this.busy = true;
|
|
var begin = Date.now();
|
|
wx.vibrateShort({ type: "heavy", });
|
|
var gd = getApp().globalData;
|
|
gd.image_data_url = data_url;
|
|
gd.qr_code = qr_code;
|
|
gd.angle = angle;
|
|
this.log("uploading image");
|
|
const fail = (e) => {
|
|
info("upload failed, goto not found: " + JSON.stringify(e));
|
|
this.show_verifyfailed();
|
|
};
|
|
const success = (res) => {
|
|
info("upload success, code", res.statusCode);
|
|
this.log("upload success, code", res.statusCode);
|
|
if (res.statusCode == 200) {
|
|
var resp;
|
|
if (typeof res.data == "string") {
|
|
resp = JSON.parse(res.data);
|
|
} else {
|
|
resp = res.data;
|
|
}
|
|
this.log(resp);
|
|
info("resp", resp);
|
|
gd.verify_resp = resp;
|
|
if (resp.serial_code) {
|
|
// Let the first part of the loading animation more "noticable" and
|
|
// the ui experience less "jumpy"
|
|
var delay = 3000 - (Date.now() - begin);
|
|
setTimeout(() => {
|
|
this.show_result_page(resp.serial_code);
|
|
}, delay > 0 ? delay : 0);
|
|
} else if (gd.debug && resp.info == "ok") {
|
|
// this is from the debug backend sigifying the upload has succeeded
|
|
// we have no product info to show here because "ok" is the only returned information
|
|
// just show a temporary page
|
|
info("redirecting to debuguploaded");
|
|
wx.redirectTo({ url: '/pages/debuguploaded/debuguploaded' })
|
|
} else {
|
|
this.show_verifyfailed();
|
|
}
|
|
} else {
|
|
info("invalid status code");
|
|
this.show_verifyfailed();
|
|
}
|
|
};
|
|
upload_image_data_url(data_url, success, fail, this.data.logs.join("\n"));
|
|
},
|
|
|
|
show_result_page(serial_code) {
|
|
this.busy = true;
|
|
wx.redirectTo({
|
|
url: '/pages/productinfo/productinfo?serial_code=' + serial_code,
|
|
});
|
|
},
|
|
|
|
show_verifyfailed() {
|
|
this.busy = true;
|
|
this.setData({
|
|
show_modal: "verifyfailed",
|
|
});
|
|
},
|
|
|
|
show_service() {
|
|
this.busy = true;
|
|
this.setData({
|
|
show_modal: "service",
|
|
});
|
|
},
|
|
|
|
show_scanguide() {
|
|
this.busy = true;
|
|
this.setData({
|
|
show_modal: "scanguide",
|
|
});
|
|
},
|
|
|
|
restart_camera() {
|
|
wx.redirectTo({
|
|
url: '/pages/camera/camera',
|
|
})
|
|
},
|
|
|
|
reset_busy() {
|
|
setTimeout(() => {
|
|
this.busy = false;
|
|
}, 20)
|
|
},
|
|
|
|
setup_camera: function (opts) {
|
|
var max_zoom = null;
|
|
if (opts) {
|
|
console.log(opts);
|
|
max_zoom = opts.detail.maxZoom;
|
|
this.setData({
|
|
max_zoom,
|
|
});
|
|
}
|
|
get_camera_rule(max_zoom, (rule) => {
|
|
var zoom = rule.zoom;
|
|
var initial_zoom = 2;
|
|
this.setData({
|
|
zoom: initial_zoom,
|
|
rule_zoom: zoom,
|
|
});
|
|
const ctx = wx.createCameraContext();
|
|
this.log(`camera set initial zoom to ${initial_zoom}x, will zoom in to ${rule.zoom}x when qr is found`);
|
|
ctx.setZoom({ zoom: initial_zoom });
|
|
this.on_qr_found = () => {
|
|
this.log(`qr found, zoom to ${rule.zoom}x`);
|
|
ctx.setZoom({ zoom: rule.zoom });
|
|
this.setData({
|
|
zoom: rule.zoom,
|
|
qrmarkers_class: "",
|
|
qrarc_class: "lg"
|
|
});
|
|
}
|
|
if (!this.listener) {
|
|
this.log("creating camera frame listener...");
|
|
const listener = ctx.onCameraFrame((res) => { this.handle_frame(res) });
|
|
this.listener = listener;
|
|
}
|
|
const worker = getApp().globalData.worker;
|
|
this.listener.start({
|
|
worker,
|
|
});
|
|
});
|
|
},
|
|
|
|
handle_frame(res) {
|
|
if (this.failed) return;
|
|
try {
|
|
this.do_handle_frame(res);
|
|
} catch (e) {
|
|
console.log(e);
|
|
this.failed = true;
|
|
}
|
|
},
|
|
handle_image_data_url(data_url) {
|
|
const now = Date.now();
|
|
if (
|
|
now - this.data.last_frame_upload_time < this.data.frame_upload_interval_ms ||
|
|
this.data.frame_upload_in_flight > 0
|
|
) {
|
|
return;
|
|
}
|
|
var begin = Date.now();
|
|
this.setData({
|
|
last_frame_upload_time: now,
|
|
frame_upload_seq_num: this.data.frame_upload_seq_num + 1,
|
|
frame_upload_in_flight: this.data.frame_upload_in_flight + 1,
|
|
});
|
|
var seq_num = this.data.frame_upload_seq_num.toString().padStart(3, '0');
|
|
var fd = {
|
|
session_id: getApp().globalData.session_id,
|
|
phone_model: getApp().globalData.phone_model,
|
|
seq_num: seq_num,
|
|
image_data_url: data_url,
|
|
}
|
|
wx.request({
|
|
url: "https://research.themblem.com/event/camera-frame",
|
|
method: "POST",
|
|
data: JSON.stringify(fd),
|
|
header: {
|
|
"Content-Type": "application/json",
|
|
},
|
|
success: (res) => {
|
|
this.setData({
|
|
frame_uploaded: this.data.frame_uploaded + 1,
|
|
frame_upload_in_flight: this.data.frame_upload_in_flight - 1,
|
|
frame_upload_time_cost: Date.now() - begin,
|
|
frame_upload_interval_ms: this.data.frame_upload_interval_ms * 2,
|
|
});
|
|
},
|
|
fail: (e) => {
|
|
this.log("frame upload failed", e);
|
|
this.setData({
|
|
frame_upload_in_flight: this.data.frame_upload_in_flight - 1,
|
|
frame_upload_time_cost: Date.now() - begin,
|
|
frame_upload_interval_ms: this.data.frame_upload_interval_ms * 2,
|
|
});
|
|
},
|
|
});
|
|
},
|
|
do_handle_frame(res) {
|
|
if (this.busy) return;
|
|
this.busy = true;
|
|
if (this.data.use_worker) {
|
|
this.processing_begin = Date.now();
|
|
console.log("postMessage frame");
|
|
this.worker.postMessage({
|
|
type: 'frame',
|
|
width: res.width,
|
|
height: res.height,
|
|
camera_sensitivity: this.data.camera_sensitivity,
|
|
});
|
|
} else {
|
|
/* Make sure we are copying the frame data to avoid TOCTOU race condition
|
|
* with camera event (update of res.data while we're working) */
|
|
var uca1 = new Uint8ClampedArray(res.data);
|
|
var uca = new Uint8ClampedArray(uca1);
|
|
const data_url = data_url_from_frame(res.width, res.height, uca);
|
|
var r = frame_pre_check(res.width, res.height, uca, this.data.camera_sensitivity);
|
|
const result = r.result;
|
|
this.log("frame_pre_check: ", JSON.stringify(result));
|
|
this.setData({
|
|
result: JSON.stringify(r.result),
|
|
debug_image_data_url: r.data_url,
|
|
});
|
|
this.handle_image_data_url(data_url);
|
|
if (this.data.done_checking_auto_torch && result.ok && result.angle >= 0 && result.qrcode.length && is_emblem_qr_pattern(result.qrcode)) {
|
|
this.setData({
|
|
show_modal: "verifying",
|
|
});
|
|
this.submit_image(data_url, result.qrcode, result.angle);
|
|
} else {
|
|
if (this.data.should_check_auto_torch && is_emblem_qr_pattern(result.qrcode)) {
|
|
this.start_check_auto_torch(result.qrcode);
|
|
}
|
|
this.pending_hint_text = this.make_hint_text(result);
|
|
this.reset_busy();
|
|
}
|
|
}
|
|
},
|
|
|
|
torch_on() {
|
|
this.log("torch on");
|
|
this.setData({
|
|
camera_flash: "torch",
|
|
});
|
|
},
|
|
|
|
toggle_torch() {
|
|
this.log("toggle torch");
|
|
var cf = this.data.camera_flash == "torch" ? "off" : "torch";
|
|
this.setData({
|
|
camera_flash: cf,
|
|
});
|
|
},
|
|
|
|
debug_tap() {
|
|
if (this.data.enable_debug) {
|
|
return;
|
|
}
|
|
this.data.debug_countdown -= 1;
|
|
if (this.data.debug_countdown <= 0) {
|
|
this.log("enabling debug");
|
|
this.setData({
|
|
enable_debug: true,
|
|
})
|
|
}
|
|
},
|
|
})
|