Measured on real hardware: IO1=right IO2=left IO3=top IO4=middle IO5=bottom
(0x01=right 0x02=left 0x04=top 0x08=middle 0x10=bottom). The earlier verbal
'left=IO1/upper=IO2...' spec was wrong; this reverts to the original Phase-1
mapping, which the measurement confirms.
- protocol.js REGION constants + getRegionName + REGION_NAMES
- CRITICAL: buildVendorCommand ioMasks was built from REGION.* constants whose
values just flipped, scrambling IO slot order; pin to literal [0x01..0x10]
- diagnosis.js REGION_DEFS key↔mask; pd_region_map values unchanged (PD is
keyed by orientation, which is stable) — right=PD5 left=PD1 top=PD2
middle=PD3,6,7 bottom=PD4
- common.js region labels back to right/left/top/middle/bottom (上/中/下)
- treatment-setup / manual-treatment / treating / scan-report / ble-debug
face-map bindings and REGION_MAP realigned
- verified: mock pipeline lands left→IO2, middle→IO4 correctly
- entry in profile menu, visible only while a device is connected
- live FFE4 readout: 7x PD, VBAT, fitting, scan wave marker, run state
- tap a face-map region to fire a 2s single-region treat command
(verifies region-to-IO mapping on the physical mask)
- wavelength selector (red/ir/uv/yellow) to verify color byte order
- scan command / full-face 2s / stop buttons, hex log of every write
- intentionally not i18n'd; remove page before release
- scan-report: replace zone card grid with the same oval face-map layout as
treatment-setup, zones tinted by recommended light with mode label; restore
the per-light legend under the map
- manual-treatment: region picker now uses the same face-map layout
- manual care mode selection is smart-mode only: entry link hidden for
non-subscribers and the page itself checks subscription (free mode passes
since the backend reports it as active)
Server: subscription API returns active when smart_mode_free is true.
Admin console: add toggle switch in settings page under feature flags.
Default: true (free for all users during promotion period).
- Add "start detection" button (not auto-start on page load)
- Send control=0x01 only when user clicks button
- Accumulate PD values across all ADC frames during scan
- Monitor run_state: wait for 0x01→0x00 transition as completion signal
- Calculate PD averages on completion
- Send treatment command (control=0x02) on "next" with calculated params
- TODO: plug in full NR diagnosis algorithm when PD→region mapping confirmed
Remove all fitting detection logic (setParams, fitting event listener,
timeout). User manually confirms wearing, then proceeds to detection
page. No BLE commands sent on this page.
- Remove parseVendorStatus 17-byte branch (FFE4 is now 19 bytes)
- Remove parseVendorStatus 33-byte branch (FFE1 is now 34 bytes)
- Remove buildVendorCommand33 deprecated alias
- Remove fitting/run_state !== -1 guards in connection.js
- Update commands.js to use buildVendorCommand directly
wear-check:
- Send weak pulse light (brightness=26) to trigger FFE4 fitting report
- Timeout degrades to pass (backward compat with 17-byte firmware)
- Sync _resolved flag prevents double-resolve
treating:
- Only treat run_state=0x00 as completion after seeing run_state=0x02
(prevents false completion on startup)
- Sync _finishing flag replaces async data.completed for race prevention
- onStatus preserves paused state during fitting loss (|| fittingLost)
- Remove dead _fittingTimer reference from onUnload
auto-scan:
- Sync _completed flag in _scanComplete prevents double-build
- Listen for 'fitting' event: detect skin detachment, pause on loss,
resume on re-attach
- Listen for 'run_state' event: detect device fault (0xFF) and IDLE
completion (0x00)
- Low battery warnings: toast at <=10%, force stop at <=5%
- Guard finishAsComplete against double-completion
- Proper cleanup of all new event listeners on unload
Firmware does R→UV→Y→IR sequential scan autonomously. Miniprogram
now listens for 4 consecutive ADC notifications, storing each as a
separate spectrum group. Also listens for run_state=IDLE as scan
completion signal. Displays all 4 spectrum groups with PD1-PD7 bars.
Stores full scan data in globalData.lastScanData for diagnosis phase.
Listen for 'fitting' event from FFE4 Byte17 (0=not fitted, 1=fitted).
10s timeout if no fitting signal received. Removes vendor_33 bypass
that always returned ok.