Files
Wx_BLWConfigTools_V02_Prod/utils/w13Packet.js
2026-01-13 15:37:51 +08:00

164 lines
5.0 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// W13 BLE 协议公共组包模块
// 导出通用的组包构造与CRC16计算支持 CCITT-FALSE 与 MODBUS
const DEFAULT_HEAD = [0xCC, 0xC0];
const MAX_LEN = 548; // 文档声明的最大总长度含包头与CRC
export const COMMANDS = {
READ_VERSION: 0x01,
SET_CONDITION_1: 0x08,
SET_CONDITION_2: 0x09,
OTA_START: 0x0B,
ENABLE_BLE_LOG: 0x0C,
RADAR_STATUS: 0x11,
TEST_KEYS: 0x13,
};
function toUint16LE(v) {
const x = v & 0xFFFF;
return [x & 0xFF, (x >>> 8) & 0xFF];
}
function ensureUint8Array(data) {
if (!data) return new Uint8Array(0);
if (data instanceof Uint8Array) return data;
if (Array.isArray(data)) return Uint8Array.from(data.map(x => x & 0xFF));
if (typeof data === 'string') {
// 默认按ASCII编码如需GBK请先外部转换后传入字节数组
const arr = new Uint8Array(data.length);
for (let i = 0; i < data.length; i++) arr[i] = data.charCodeAt(i) & 0xFF;
return arr;
}
throw new TypeError('Unsupported payload type');
}
// CRC-16/CCITT-FALSE: poly=0x1021, init=0xFFFF, refin=false, refout=false, xorout=0x0000
export function crc16Ccitt(bytes, start = 0, end = bytes.length) {
let crc = 0xFFFF;
for (let i = start; i < end; i++) {
crc ^= (bytes[i] << 8);
for (let j = 0; j < 8; j++) {
if (crc & 0x8000) crc = (crc << 1) ^ 0x1021; else crc <<= 1;
crc &= 0xFFFF;
}
}
return crc & 0xFFFF;
}
// CRC-16/MODBUS: poly=0xA001, init=0xFFFF, refin=true, refout=true, xorout=0x0000
export function crc16Modbus(bytes, start = 0, end = bytes.length) {
let crc = 0xFFFF;
for (let i = start; i < end; i++) {
crc ^= bytes[i];
for (let j = 0; j < 8; j++) {
const lsb = crc & 0x0001;
crc >>= 1;
if (lsb) crc ^= 0xA001;
}
}
return crc & 0xFFFF;
}
function computeCrc(bytes, type, range) {
const [start, end] = range || [0, bytes.length];
if (type === 'MODBUS') return crc16Modbus(bytes, start, end);
return crc16Ccitt(bytes, start, end);
}
/**
* 构造W13协议数据帧
* 字段Head(2) + Len(2) + CRC(2) + Frame(2) + FramNum(2) + Frame_Type(1) + Parameters(N)
* Len为整包总长度含Head与CRC小端CRC16默认CCITT-FALSE计算范围为整包除CRC字段自身。
*
* @param {Object} opts
* @param {number} opts.frame 帧号(0~65535)
* @param {number} opts.framNum 帧总数(0~65535)
* @param {number} opts.type 命令字(0~255)
* @param {Uint8Array|number[]|string} [opts.params] 参数字节或ASCII字符串
* @param {('CCITT'|'MODBUS')} [opts.crcType] CRC算法选择默认CCITT
* @param {number[]} [opts.head] 包头,默认[0xCC,0xC0]
* @returns {Uint8Array}
*/
export function buildPacket(opts) {
const head = (opts && opts.head) || DEFAULT_HEAD;
const frame = (opts && opts.frame) != null ? (opts.frame >>> 0) : 1;
const framNum = (opts && opts.framNum) != null ? (opts.framNum >>> 0) : 1;
const type = (opts && opts.type) != null ? (opts.type & 0xFF) : 0x00;
const params = ensureUint8Array(opts && opts.params);
const crcType = (opts && opts.crcType) === 'MODBUS' ? 'MODBUS' : 'CCITT';
// 预分配:固定头(2) + 长度(2) + CRC(2) + Frame(2) + FramNum(2) + Type(1) + 参数
const fixedLen = 2 + 2 + 2 + 2 + 2 + 1;
const totalLen = fixedLen + params.length;
if (totalLen > MAX_LEN) throw new RangeError(`Packet too long: ${totalLen} > ${MAX_LEN}`);
const buf = new Uint8Array(totalLen);
let off = 0;
// Head
buf[off++] = head[0] & 0xFF;
buf[off++] = head[1] & 0xFF;
// Len (占位后写入)
const lenLE = toUint16LE(totalLen);
buf[off++] = lenLE[0];
buf[off++] = lenLE[1];
// CRC (占位,稍后计算写入)
const crcPos = off;
buf[off++] = 0x00;
buf[off++] = 0x00;
// Frame
const frameLE = toUint16LE(frame);
buf[off++] = frameLE[0];
buf[off++] = frameLE[1];
// FramNum
const framNumLE = toUint16LE(framNum);
buf[off++] = framNumLE[0];
buf[off++] = framNumLE[1];
// Frame_Type
buf[off++] = type;
// Parameters
buf.set(params, off);
// 计算CRC整包除CRC字段自身
// 为避免实现差异计算时将CRC位置的两个字节视为0
buf[crcPos] = 0x00;
buf[crcPos + 1] = 0x00;
const crc = computeCrc(buf, crcType, [0, buf.length]);
const crcLE = toUint16LE(crc);
buf[crcPos] = crcLE[0];
buf[crcPos + 1] = crcLE[1];
return buf;
}
/**
* 便捷构造:使用命令常量与简易参数数组
* @param {number} cmd COMMANDS中的命令字
* @param {number[]|Uint8Array|string} payload 参数
* @param {{frame?:number, framNum?:number, crcType?:('CCITT'|'MODBUS'), head?:number[]}} [options]
*/
export function buildCommand(cmd, payload, options = {}) {
return buildPacket({
frame: options.frame != null ? options.frame : 1,
framNum: options.framNum != null ? options.framNum : 1,
type: cmd & 0xFF,
params: payload,
crcType: options.crcType === 'MODBUS' ? 'MODBUS' : 'CCITT',
head: options.head || DEFAULT_HEAD,
});
}
/**
* 示例:构造“读版本号”命令帧 (Frame_Type=0x01, P0=0x00)
* @returns {Uint8Array}
*/
export function buildReadVersion() {
return buildCommand(COMMANDS.READ_VERSION, [0x00]);
}