diff --git a/miniprogram/app.json b/miniprogram/app.json
index d82bdef..872039a 100644
--- a/miniprogram/app.json
+++ b/miniprogram/app.json
@@ -20,7 +20,8 @@
"pages/contact/contact",
"pages/light-info/light-info",
"pages/manual-treatment/manual-treatment",
- "pages/scan-report/scan-report"
+ "pages/scan-report/scan-report",
+ "pages/ble-debug/ble-debug"
],
"window": {
"navigationBarBackgroundColor": "#ffffff",
@@ -65,4 +66,4 @@
],
"style": "v2",
"sitemapLocation": "sitemap.json"
-}
+}
\ No newline at end of file
diff --git a/miniprogram/pages/ble-debug/ble-debug.js b/miniprogram/pages/ble-debug/ble-debug.js
new file mode 100644
index 0000000..fd2c768
--- /dev/null
+++ b/miniprogram/pages/ble-debug/ble-debug.js
@@ -0,0 +1,130 @@
+// TEMPORARY debug page for on-device verification (region↔IO mapping, color byte
+// order, scan wave bucketing, PD polarity). Guoguo-only tooling — intentionally
+// NOT i18n'd; remove this page before any release build.
+var ble = require('../../services/ble')
+var protocol = require('../../services/ble/protocol')
+
+var WAVES = [
+ { key: 'red', code: 2, label: '红光' },
+ { key: 'ir', code: 1, label: '红外' },
+ { key: 'uv', code: 3, label: '紫外' },
+ { key: 'yellow', code: 4, label: '暖黄' }
+]
+var REGION_LABELS = { 1: '左区/IO1', 2: '中上/IO2', 4: '中区/IO3', 8: '中下/IO4', 16: '右区/IO5' }
+var RUN_STATES = { 0: 'IDLE', 1: '检测', 2: '治疗', 255: '故障(0xFF)' }
+
+function hex(bytes) {
+ return bytes.map(function (b) { return ('0' + b.toString(16)).slice(-2).toUpperCase() }).join(' ')
+}
+
+Page({
+ data: {
+ statusBarHeight: 44,
+ connected: false,
+ waves: WAVES,
+ selectedWave: 'red',
+ // live FFE4 status
+ pd: [], vbat: 0, battery: 0, fitting: -1, scanWave: '-', runState: '-', frames: 0,
+ logs: []
+ },
+
+ onLoad: function () {
+ this.setData({ statusBarHeight: getApp().globalData.statusBarHeight })
+ },
+
+ onShow: function () {
+ var self = this
+ self.setData({ connected: ble.isConnected() })
+ self._onAdc = function (d) {
+ self.setData({
+ pd: d.pd || [],
+ vbat: d.vbat || 0,
+ battery: d.battery || 0,
+ fitting: d.fitting,
+ scanWave: d.scan_wave || '-',
+ runState: RUN_STATES[d.run_state] !== undefined ? RUN_STATES[d.run_state] : String(d.run_state),
+ frames: self.data.frames + 1
+ })
+ }
+ ble.on('adc', self._onAdc)
+ },
+
+ onHide: function () { this._cleanup() },
+ onUnload: function () { this._cleanup() },
+ _cleanup: function () {
+ if (this._onAdc) { ble.off('adc', this._onAdc); this._onAdc = null }
+ },
+
+ onBack: function () {
+ wx.navigateBack({ fail: function () { wx.switchTab({ url: '/pages/profile/profile' }) } })
+ },
+
+ _log: function (msg) {
+ var logs = this.data.logs
+ logs.unshift(new Date().toTimeString().slice(0, 8) + ' ' + msg)
+ if (logs.length > 20) logs.pop()
+ this.setData({ logs: logs })
+ },
+
+ _send: function (label, options) {
+ var self = this
+ if (!ble.isConnected()) {
+ self.setData({ connected: false })
+ wx.showToast({ title: '设备未连接', icon: 'none' })
+ return
+ }
+ var bytes = protocol.buildVendorCommand(options)
+ self._log(label)
+ self._log('→ ' + hex(bytes))
+ ble.setParams(options).then(function () {
+ self._log('✓ 写入成功')
+ }).catch(function (err) {
+ self._log('✗ 写入失败: ' + ((err && err.msg) || JSON.stringify(err)))
+ })
+ },
+
+ onSelectWave: function (e) {
+ this.setData({ selectedWave: e.currentTarget.dataset.key })
+ },
+
+ _waveCode: function () {
+ var key = this.data.selectedWave
+ for (var i = 0; i < WAVES.length; i++) if (WAVES[i].key === key) return WAVES[i].code
+ return 2
+ },
+
+ // 点脸型图分区:该区 2 秒治疗(验证物理位置对不对)
+ onTapRegion: function (e) {
+ var mask = parseInt(e.currentTarget.dataset.mask)
+ this._send('治疗 2s ' + REGION_LABELS[mask] + ' ' + this.data.selectedWave, {
+ region_mask: mask, wavelength: this._waveCode(), brightness: 204,
+ hold_time: 2, control: 0x02
+ })
+ },
+
+ // 检测指令(与 auto-scan 同参:红光 128 全脸,设备自跑序贯扫描)
+ onScanCmd: function () {
+ this._send('检测指令 control=0x01', {
+ region_mask: 0x1F, wavelength: 2, brightness: 128,
+ hold_time: 0, control: 0x01
+ })
+ },
+
+ // 全脸当前波长 2 秒
+ onTreatAll: function () {
+ this._send('治疗 2s 全脸 ' + this.data.selectedWave, {
+ region_mask: 0x1F, wavelength: this._waveCode(), brightness: 204,
+ hold_time: 2, control: 0x02
+ })
+ },
+
+ // 停止 / IDLE
+ onStop: function () {
+ var self = this
+ self._log('停止 → IDLE 全零帧')
+ ble.stopTreatment().then(function () { self._log('✓ 已停止') })
+ .catch(function (err) { self._log('✗ 停止失败: ' + ((err && err.msg) || '')) })
+ },
+
+ onClearLog: function () { this.setData({ logs: [], frames: 0 }) }
+})
diff --git a/miniprogram/pages/ble-debug/ble-debug.json b/miniprogram/pages/ble-debug/ble-debug.json
new file mode 100644
index 0000000..65aeba3
--- /dev/null
+++ b/miniprogram/pages/ble-debug/ble-debug.json
@@ -0,0 +1,4 @@
+{
+ "navigationStyle": "custom",
+ "navigationBarTitleText": "调试工具"
+}
diff --git a/miniprogram/pages/ble-debug/ble-debug.wxml b/miniprogram/pages/ble-debug/ble-debug.wxml
new file mode 100644
index 0000000..6a36630
--- /dev/null
+++ b/miniprogram/pages/ble-debug/ble-debug.wxml
@@ -0,0 +1,65 @@
+
+
+
+
+
+
+
+ FFE4 实时状态 ({{frames}} 帧)
+
+
+ PD{{index + 1}}
+ {{item}}
+
+
+
+ VBAT {{vbat}}mV ({{battery}}%)
+ 贴合 {{fitting}}
+ 光色 {{scanWave}}
+ 状态 {{runState}}
+
+
+
+
+
+ 波长(点分区/全脸时使用)
+
+ {{item.label}}
+
+
+
+
+
+ 点分区 → 该区 2 秒治疗(对物理位置)
+
+ 中上\nIO2
+ 左\nIO1
+ 右\nIO5
+ 中\nIO3
+ 中下\nIO4
+
+
+
+
+
+ 指令
+
+
+
+
+
+
+
+
+
+ 发送日志 清空
+ {{item}}
+ 暂无
+
+
+
diff --git a/miniprogram/pages/ble-debug/ble-debug.wxss b/miniprogram/pages/ble-debug/ble-debug.wxss
new file mode 100644
index 0000000..ba8bc0f
--- /dev/null
+++ b/miniprogram/pages/ble-debug/ble-debug.wxss
@@ -0,0 +1,39 @@
+/* 临时调试页样式(上线前随页面一并移除) */
+.page { min-height: 100vh; background: #f5f6f8; }
+.page-content { padding: 24rpx; }
+
+.card { background: #fff; border-radius: 24rpx; padding: 24rpx; margin-bottom: 20rpx; }
+.card-title { font-size: 28rpx; font-weight: 600; color: #333; margin-bottom: 16rpx; }
+.frames { font-size: 22rpx; color: #999; font-weight: 400; }
+
+.pd-row { display: flex; flex-wrap: wrap; gap: 12rpx; }
+.pd-cell { flex: 0 0 calc((100% - 72rpx) / 7); text-align: center; background: #FAFAFA; border-radius: 12rpx; padding: 10rpx 0; }
+.pd-idx { font-size: 20rpx; color: #999; }
+.pd-val { font-size: 24rpx; color: #333; font-weight: 600; }
+.status-row { display: flex; flex-wrap: wrap; gap: 20rpx; margin-top: 16rpx; font-size: 24rpx; color: #666; }
+
+.wave-row { display: flex; gap: 16rpx; }
+.wave-chip { flex: 1; text-align: center; padding: 18rpx 0; border: 4rpx solid #e5e5e5; border-radius: 20rpx; font-size: 26rpx; color: #999; }
+.wave-chip.active { border-color: #E6508C; background: rgba(230, 80, 140, 0.12); color: #E6508C; }
+
+.face-map { width: 280rpx; height: 340rpx; margin: 0 auto; position: relative; }
+.face-region {
+ position: absolute; border: 4rpx solid #d5d5d5; border-radius: 50%;
+ display: flex; align-items: center; justify-content: center; text-align: center;
+ font-size: 22rpx; color: #666; background: #FAFAFA; white-space: pre-line; line-height: 1.3;
+}
+.face-region:active { background: rgba(230, 80, 140, 0.25); border-color: #E6508C; color: #E6508C; }
+.face-region.forehead { top: 5%; left: 25%; width: 50%; height: 15%; border-radius: 40rpx 40rpx 50% 50%; }
+.face-region.left-cheek { top: 25%; left: 5%; width: 30%; height: 25%; }
+.face-region.right-cheek { top: 25%; right: 5%; width: 30%; height: 25%; }
+.face-region.nose { top: 45%; left: 35%; width: 30%; height: 18%; border-radius: 40%; }
+.face-region.chin { top: 68%; left: 25%; width: 50%; height: 20%; border-radius: 0 0 50% 50%; }
+
+.btn-row { display: flex; gap: 16rpx; }
+.dbg-btn { flex: 1; font-size: 26rpx; padding: 0; line-height: 76rpx; border-radius: 20rpx; background: #4A90D9; color: #fff; }
+.dbg-btn::after { border: none; }
+.dbg-btn-stop { background: #E8503A; }
+
+.log-clear { float: right; font-size: 22rpx; color: #4A90D9; font-weight: 400; }
+.log-line { font-family: monospace; font-size: 20rpx; color: #555; padding: 4rpx 0; border-bottom: 1rpx solid #f2f2f2; word-break: break-all; }
+.log-empty { font-size: 22rpx; color: #bbb; }
diff --git a/miniprogram/pages/profile/profile.js b/miniprogram/pages/profile/profile.js
index 24d8619..ba2d113 100644
--- a/miniprogram/pages/profile/profile.js
+++ b/miniprogram/pages/profile/profile.js
@@ -19,6 +19,13 @@ Page({
onShow: function () {
i18n.bind(this)
this.loadProfile()
+ // 临时调试入口:仅设备已连接时显示(上线前随 ble-debug 页一并移除)
+ var ble = require('../../services/ble')
+ this.setData({ debugVisible: ble.isConnected() })
+ },
+
+ onOpenDebug: function () {
+ wx.navigateTo({ url: '/pages/ble-debug/ble-debug' })
},
onLightInfo: function () {
diff --git a/miniprogram/pages/profile/profile.wxml b/miniprogram/pages/profile/profile.wxml
index a15ec08..cc746e9 100644
--- a/miniprogram/pages/profile/profile.wxml
+++ b/miniprogram/pages/profile/profile.wxml
@@ -50,6 +50,12 @@
+
+