feat: upgrade FFE1 command to 34 bytes with control byte

Per 协议简述(3): Byte33 is now control instruction (0=IDLE, 1=scan,
2=treat), Byte34 is XOR checksum. Backward compat with 33-byte
read-back preserved. All command sites updated with correct control:
- auto-scan: control=0x01 (scan mode)
- treatment-setup: control=0x02 (treat mode)
- stopTreatment: control=0x00 (IDLE)
这个提交包含在:
Guoguo
2026-06-12 09:00:01 -07:00
父节点 69621fd2b6
当前提交 36f12e098e
修改 5 个文件,包含 50 行新增6 行删除
+2 -1
查看文件
@@ -67,7 +67,8 @@ Page({
region_mask: 0x1F, region_mask: 0x1F,
wavelength: 2, wavelength: 2,
brightness: SCAN_BRIGHTNESS, brightness: SCAN_BRIGHTNESS,
hold_time: 10 hold_time: 10,
control: 0x01
}).then(function () { }).then(function () {
console.log('[SCAN] 扫描指令发送成功,等待ADC回传...') console.log('[SCAN] 扫描指令发送成功,等待ADC回传...')
}).catch(function (err) { }).catch(function (err) {
@@ -90,7 +90,8 @@ Page({
wavelength: 2, wavelength: 2,
brightness: 200, brightness: 200,
duration_ms: self.data.fixedDurationMs, duration_ms: self.data.fixedDurationMs,
mode: self.data.selectedMode mode: self.data.selectedMode,
control: 0x02
}).then(function () { }).then(function () {
return ble.startTreatment(mask) return ble.startTreatment(mask)
}).then(function () { }).then(function () {
+3 -2
查看文件
@@ -181,12 +181,13 @@ function startTreatment(regionMask) {
function stopTreatment() { function stopTreatment() {
if (protocol.PROTOCOL_MODE === 'vendor_33') { if (protocol.PROTOCOL_MODE === 'vendor_33') {
return writeRawCommand(protocol.buildVendorCommand33({ return writeRawCommand(protocol.buildVendorCommand({
region_mask: 0, region_mask: 0,
wavelength: protocol.WAVELENGTH.R, wavelength: protocol.WAVELENGTH.R,
brightness: 0, brightness: 0,
current_gain: 0, current_gain: 0,
hold_time: 0 hold_time: 0,
control: protocol.CONTROL.IDLE
})) }))
} }
return writeCommandWithRetry(protocol.CMD.STOP, []) return writeCommandWithRetry(protocol.CMD.STOP, [])
+2
查看文件
@@ -15,6 +15,7 @@ module.exports = {
WAVELENGTH: protocol.WAVELENGTH, WAVELENGTH: protocol.WAVELENGTH,
TREAT_MODE: protocol.TREAT_MODE, TREAT_MODE: protocol.TREAT_MODE,
REGION: protocol.REGION, REGION: protocol.REGION,
CONTROL: protocol.CONTROL,
DEVICE_ERR: protocol.DEVICE_ERR, DEVICE_ERR: protocol.DEVICE_ERR,
// Connection // Connection
@@ -41,6 +42,7 @@ module.exports = {
PROTOCOL_MODE: protocol.PROTOCOL_MODE, PROTOCOL_MODE: protocol.PROTOCOL_MODE,
buildFrame: protocol.buildFrame, buildFrame: protocol.buildFrame,
parseFrame: protocol.parseFrame, parseFrame: protocol.parseFrame,
buildVendorCommand: protocol.buildVendorCommand,
buildVendorCommand33: protocol.buildVendorCommand33, buildVendorCommand33: protocol.buildVendorCommand33,
parseVendorStatus: protocol.parseVendorStatus, parseVendorStatus: protocol.parseVendorStatus,
parseStatusReport: protocol.parseStatusReport, parseStatusReport: protocol.parseStatusReport,
+41 -2
查看文件
@@ -155,9 +155,19 @@ function bytesToHex(bytes) {
return hex.toUpperCase() return hex.toUpperCase()
} }
// --- vendor 33-byte command protocol --- // --- vendor 34-byte command protocol ---
// Byte1-30: IO1~IO5 (6B each: R, IR, UV, Y, GainH, GainL)
// Byte31-32: hold time (seconds, big-endian)
// Byte33: control (0=IDLE, 1=scan, 2=treat)
// Byte34: XOR checksum of Byte1-33
function buildVendorCommand33(options) { var CONTROL = {
IDLE: 0x00,
SCAN: 0x01,
TREAT: 0x02
}
function buildVendorCommand(options) {
options = options || {} options = options || {}
var regionMask = options.region_mask || REGION.FULL_FACE var regionMask = options.region_mask || REGION.FULL_FACE
var wavelength = options.wavelength || WAVELENGTH.R var wavelength = options.wavelength || WAVELENGTH.R
@@ -165,6 +175,7 @@ function buildVendorCommand33(options) {
var gain = clampUInt16(options.current_gain === undefined ? brightness : options.current_gain) var gain = clampUInt16(options.current_gain === undefined ? brightness : options.current_gain)
var durationMs = options.duration_ms || 600000 var durationMs = options.duration_ms || 600000
var holdSeconds = clampUInt16(options.hold_time === undefined ? Math.round(durationMs / 1000) : options.hold_time) var holdSeconds = clampUInt16(options.hold_time === undefined ? Math.round(durationMs / 1000) : options.hold_time)
var control = options.control !== undefined ? options.control : CONTROL.TREAT
var regions = [ var regions = [
REGION.LEFT_CHEEK, REGION.LEFT_CHEEK,
REGION.RIGHT_CHEEK, REGION.RIGHT_CHEEK,
@@ -190,10 +201,15 @@ function buildVendorCommand33(options) {
} }
bytes.push((holdSeconds >> 8) & 0xFF, holdSeconds & 0xFF) bytes.push((holdSeconds >> 8) & 0xFF, holdSeconds & 0xFF)
bytes.push(control & 0xFF)
bytes.push(xorChecksum(bytes)) bytes.push(xorChecksum(bytes))
return bytes return bytes
} }
function buildVendorCommand33(options) {
return buildVendorCommand(options)
}
function parseVendorStatus(buffer) { function parseVendorStatus(buffer) {
var bytes = bufferToBytes(buffer) var bytes = bufferToBytes(buffer)
if (bytes.length === 1) { if (bytes.length === 1) {
@@ -233,6 +249,26 @@ function parseVendorStatus(buffer) {
raw_hex: bytesToHex(bytes) raw_hex: bytesToHex(bytes)
} }
} }
if (bytes.length === 34) {
var ios34 = []
for (var j = 0; j < 5; j++) {
var off34 = j * 6
ios34.push({
red: bytes[off34], infrared: bytes[off34 + 1],
uv: bytes[off34 + 2], warm_yellow: bytes[off34 + 3],
gain: (bytes[off34 + 4] << 8) | bytes[off34 + 5]
})
}
return {
type: 'params',
is_heartbeat: false,
ios: ios34,
hold_time: (bytes[30] << 8) | bytes[31],
control: bytes[32],
checksum: bytes[33],
raw_hex: bytesToHex(bytes)
}
}
if (bytes.length === 33) { if (bytes.length === 33) {
var ios = [] var ios = []
for (var i = 0; i < 5; i++) { for (var i = 0; i < 5; i++) {
@@ -248,6 +284,7 @@ function parseVendorStatus(buffer) {
is_heartbeat: false, is_heartbeat: false,
ios: ios, ios: ios,
hold_time: (bytes[30] << 8) | bytes[31], hold_time: (bytes[30] << 8) | bytes[31],
control: -1,
checksum: bytes[32], checksum: bytes[32],
raw_hex: bytesToHex(bytes) raw_hex: bytesToHex(bytes)
} }
@@ -364,6 +401,7 @@ module.exports = {
TREAT_MODE: TREAT_MODE, TREAT_MODE: TREAT_MODE,
REGION: REGION, REGION: REGION,
REGION_NAMES: REGION_NAMES, REGION_NAMES: REGION_NAMES,
CONTROL: CONTROL,
DEVICE_ERR: DEVICE_ERR, DEVICE_ERR: DEVICE_ERR,
bufferToBytes: bufferToBytes, bufferToBytes: bufferToBytes,
@@ -373,6 +411,7 @@ module.exports = {
bytesToUint32: bytesToUint32, bytesToUint32: bytesToUint32,
hexToBytes: hexToBytes, hexToBytes: hexToBytes,
bytesToHex: bytesToHex, bytesToHex: bytesToHex,
buildVendorCommand: buildVendorCommand,
buildVendorCommand33: buildVendorCommand33, buildVendorCommand33: buildVendorCommand33,
buildFrame: buildFrame, buildFrame: buildFrame,