158 lines
4.5 KiB
JavaScript
158 lines
4.5 KiB
JavaScript
import {bytesToHex, bytesToMac} from '../utils/hex'
|
||
import {joinLowHigh, readUint16LE, toUint8Array} from '../utils/bytes'
|
||
import {DEVICE_PROFILE} from '../protocols/profiles'
|
||
|
||
/**
|
||
* 解析 UUID_STATE 的状态字节,转为有符号 8bit。
|
||
*/
|
||
export function parseStateCode(payload) {
|
||
const bytes = toUint8Array(payload)
|
||
if (!bytes.length) {
|
||
return null
|
||
}
|
||
|
||
return bytes[0] > 127 ? bytes[0] - 256 : bytes[0]
|
||
}
|
||
|
||
/**
|
||
* ED713 MSG 解析。
|
||
* 文档布局:MAC(6) + bindStatus(1) + 其它(3) + productId_H(1) + productId_L(1)
|
||
* + baseBoard(1) + coreBoard(1) + 其它(2) + deviceType(1)
|
||
* + wifiStatus(1) + cellularStatus(1) + 其它(1)
|
||
*/
|
||
function parseEd713DeviceInfo(bytes) {
|
||
if (!bytes.length) {
|
||
return {
|
||
raw: bytes, mac: '', bindStatus: null, productId: null,
|
||
deviceType: null, wifiStatus: null, cellularStatus: null
|
||
}
|
||
}
|
||
|
||
return {
|
||
raw: bytes,
|
||
mac: bytesToMac(bytes.slice(0, 6)),
|
||
bindStatus: bytes.length > 6 ? bytes[6] : null,
|
||
productId: bytes.length > 11 ? joinLowHigh(bytes[11], bytes[10]) : null,
|
||
baseBoardVersion: bytes.length > 12 ? bytes[12] : null,
|
||
coreBoardVersion: bytes.length > 13 ? bytes[13] : null,
|
||
deviceType: bytes.length > 16 ? bytes[16] : null,
|
||
wifiStatus: bytes.length > 17 ? bytes[17] : null,
|
||
cellularStatus: bytes.length > 18 ? bytes[18] : null
|
||
}
|
||
}
|
||
|
||
/**
|
||
* ED719 MSG 解析。
|
||
* 实际偏移与 ED713 一致(文档省略了 bindStatus/productId 但底板数据布局相同):
|
||
* MAC(6) + bindStatus(1) + 其它(3) + productId_H(1) + productId_L(1)
|
||
* + baseBoard(1) + coreBoard(1) + 其它(2) + deviceType(1)
|
||
* + wifiStatus(1) + cellularStatus(1) + 其它(1)
|
||
*/
|
||
function parseEd719DeviceInfo(bytes) {
|
||
return parseEd713DeviceInfo(bytes)
|
||
}
|
||
|
||
/**
|
||
* 解析 UUID_MSG 设备信息(按 profile 区分 ED713/ED719)。
|
||
*/
|
||
export function parseDeviceInfo(payload, profileId = DEVICE_PROFILE.UNKNOWN) {
|
||
const bytes = toUint8Array(payload)
|
||
|
||
if (!bytes.length) {
|
||
return {
|
||
raw: bytes, mac: '', bindStatus: null, productId: null,
|
||
deviceType: null, wifiStatus: null, cellularStatus: null
|
||
}
|
||
}
|
||
|
||
if (profileId === DEVICE_PROFILE.ED713) return parseEd713DeviceInfo(bytes)
|
||
if (profileId === DEVICE_PROFILE.ED719) return parseEd719DeviceInfo(bytes)
|
||
|
||
// UNKNOWN:按 ED713 布局兼容
|
||
return parseEd713DeviceInfo(bytes)
|
||
}
|
||
|
||
/**
|
||
* ED713 雷达数据解析。
|
||
* 协议帧长度不足时返回 valid:false,交由上层决定是否忽略。
|
||
*/
|
||
export function parseEd713Radar(payload) {
|
||
const bytes = toUint8Array(payload)
|
||
|
||
if (bytes.length < 60) {
|
||
return {
|
||
type: 'ED713',
|
||
valid: false,
|
||
reason: 'LENGTH_LT_60',
|
||
raw: bytes
|
||
}
|
||
}
|
||
|
||
return {
|
||
type: 'ED713',
|
||
valid: true,
|
||
raw: bytes,
|
||
peopleCount: bytes[24],
|
||
peopleLocalDecimeter: bytes[25],
|
||
peopleState: bytes[26],
|
||
heartRate: bytes[28],
|
||
respRate: bytes[29],
|
||
activityState: bytes[30],
|
||
moveIndex: bytes[35],
|
||
waveNum: bytes[37],
|
||
respWave: Array.from(bytes.slice(38, 58)),
|
||
narrowFlag: bytes[59]
|
||
}
|
||
}
|
||
|
||
/**
|
||
* ED719 雷达数据解析。
|
||
* 按文档结构体定义:
|
||
* header[4] + event(1) + people(1) + num(1) + nodata(1)
|
||
* + height(2) + mode(1) + beeper(1) + left(2) + right(2) + front(2) + back(2)
|
||
* + sensitive(1) + nodata1(1) + stateDelay(2) = 24 bytes
|
||
* shield_num 在 offset 24,屏蔽区在 offset 25
|
||
* 屏蔽区之后:wallOption + peopleHaveSwitch + peopleActivitySwitch
|
||
*/
|
||
export function parseEd719Radar(payload) {
|
||
const bytes = toUint8Array(payload)
|
||
|
||
if (bytes.length < 24) {
|
||
return {
|
||
type: 'ED719',
|
||
valid: false,
|
||
reason: 'LENGTH_LT_24',
|
||
raw: bytes
|
||
}
|
||
}
|
||
|
||
const shieldNum = bytes[24] || 0
|
||
const shieldStart = 25
|
||
const shieldBlockLength = Math.min(Math.max(shieldNum, 0), 4) * 9
|
||
|
||
const wallOffset = shieldStart + shieldBlockLength
|
||
|
||
return {
|
||
type: 'ED719',
|
||
valid: true,
|
||
raw: bytes,
|
||
eventState: bytes[4],
|
||
peopleState: bytes[5],
|
||
peopleNum: bytes[6],
|
||
installHeight: readUint16LE(bytes, 8),
|
||
installMode: bytes[10],
|
||
beeperOn: bytes[11],
|
||
leftDist: readUint16LE(bytes, 12),
|
||
rightDist: readUint16LE(bytes, 14),
|
||
frontDist: readUint16LE(bytes, 16),
|
||
backDist: readUint16LE(bytes, 18),
|
||
sensitive: bytes[20],
|
||
stateDelay: readUint16LE(bytes, 22),
|
||
shieldNum,
|
||
shieldZoneRaw: Array.from(bytes.slice(shieldStart, shieldStart + shieldBlockLength)),
|
||
wallOption: bytes[wallOffset] ?? null,
|
||
peopleHaveSwitch: bytes[wallOffset + 1] ?? null,
|
||
peopleActivitySwitch: bytes[wallOffset + 2] ?? null
|
||
}
|
||
}
|