feat: add user deactivation and vendor BLE protocol

这个提交包含在:
Guoguo
2026-05-11 21:14:41 +08:00
父节点 c95d47e0c1
当前提交 583fcb7e3d
修改 16 个文件,包含 698 行新增52 行删除
+67 -2
查看文件
@@ -1,5 +1,7 @@
// BLE protocol: frame encoding/decoding, checksum, constants
var PROTOCOL_MODE = 'vendor_33'
var SERVICE = {
DEVICE_INFO: 'FFE0',
DATA_COMM: 'FFE1',
@@ -8,8 +10,8 @@ var SERVICE = {
var CHAR = {
DEVICE_INFO: 'FFE3',
COMMAND: 'FFE4',
STATUS: 'FFE5',
COMMAND: 'FFE1',
STATUS: 'FFE4',
BOND_INFO: 'FFE6',
OTA_CONTROL: 'FFE7',
OTA_DATA: 'FFE8',
@@ -112,6 +114,18 @@ function xorChecksum(bytes) {
return result
}
function clampByte(value) {
var n = parseInt(value, 10)
if (isNaN(n)) n = 0
return Math.max(0, Math.min(255, n))
}
function clampUInt16(value) {
var n = parseInt(value, 10)
if (isNaN(n)) n = 0
return Math.max(0, Math.min(65535, n))
}
function uint32ToBytes(value) {
return [
(value >> 24) & 0xFF,
@@ -141,6 +155,54 @@ function bytesToHex(bytes) {
return hex.toUpperCase()
}
// --- vendor 33-byte command protocol ---
function buildVendorCommand33(options) {
options = options || {}
var regionMask = options.region_mask || REGION.FULL_FACE
var wavelength = options.wavelength || WAVELENGTH.R
var brightness = clampByte(options.brightness === undefined ? 200 : options.brightness)
var gain = clampUInt16(options.current_gain === undefined ? brightness : options.current_gain)
var durationMs = options.duration_ms || 600000
var holdSeconds = clampUInt16(options.hold_time === undefined ? Math.round(durationMs / 1000) : options.hold_time)
var regions = [
REGION.LEFT_CHEEK,
REGION.RIGHT_CHEEK,
REGION.FOREHEAD,
REGION.CHIN,
REGION.NOSE
]
var bytes = []
for (var i = 0; i < regions.length; i++) {
var enabled = (regionMask & regions[i]) !== 0
var red = 0
var infrared = 0
var uv = 0
var warmYellow = 0
if (enabled) {
if (wavelength === WAVELENGTH.R) red = brightness
if (wavelength === WAVELENGTH.IR) infrared = brightness
if (wavelength === WAVELENGTH.UV) uv = brightness
if (wavelength === WAVELENGTH.Y) warmYellow = brightness
}
bytes.push(red, infrared, uv, warmYellow, (gain >> 8) & 0xFF, gain & 0xFF)
}
bytes.push((holdSeconds >> 8) & 0xFF, holdSeconds & 0xFF)
bytes.push(xorChecksum(bytes))
return bytes
}
function parseVendorStatus(buffer) {
var bytes = bufferToBytes(buffer)
return {
mode_state: bytes.length > 0 ? bytes[0] : MODE_STATE.IDLE,
raw_bytes: bytes,
raw_hex: bytesToHex(bytes)
}
}
// --- frame encoding/decoding ---
function buildFrame(type, payload) {
@@ -240,6 +302,7 @@ function getModeStateName(code) {
}
module.exports = {
PROTOCOL_MODE: PROTOCOL_MODE,
SERVICE: SERVICE,
CHAR: CHAR,
CMD: CMD,
@@ -258,9 +321,11 @@ module.exports = {
bytesToUint32: bytesToUint32,
hexToBytes: hexToBytes,
bytesToHex: bytesToHex,
buildVendorCommand33: buildVendorCommand33,
buildFrame: buildFrame,
parseFrame: parseFrame,
parseVendorStatus: parseVendorStatus,
parseStatusReport: parseStatusReport,
parseAck: parseAck,
parseTreatmentComplete: parseTreatmentComplete,