feat: add user deactivation and vendor BLE protocol
这个提交包含在:
@@ -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,
|
||||
|
||||
在新工单中引用
屏蔽一个用户