155 lines
3.6 KiB
JavaScript
155 lines
3.6 KiB
JavaScript
/**
|
||
* 十六进制工具函数
|
||
*/
|
||
|
||
/**
|
||
* ArrayBuffer 转十六进制字符串
|
||
* @param {ArrayBuffer} buffer - ArrayBuffer 数据
|
||
* @returns {string} 十六进制字符串(大写)
|
||
*/
|
||
function arrayBufferToHex(buffer) {
|
||
const hexArr = Array.prototype.map.call(
|
||
new Uint8Array(buffer),
|
||
function (byte) {
|
||
return ('0' + byte.toString(16)).slice(-2);
|
||
}
|
||
);
|
||
return hexArr.join('').toUpperCase();
|
||
}
|
||
|
||
/**
|
||
* 十六进制字符串转 ArrayBuffer
|
||
* @param {string} hex - 十六进制字符串
|
||
* @returns {ArrayBuffer} ArrayBuffer 数据
|
||
*/
|
||
function hexToArrayBuffer(hex) {
|
||
// 去除空格和换行
|
||
hex = hex.replace(/\s+/g, '');
|
||
|
||
// 确保长度为偶数
|
||
if (hex.length % 2 !== 0) {
|
||
hex = '0' + hex;
|
||
}
|
||
|
||
const length = hex.length / 2;
|
||
const buffer = new ArrayBuffer(length);
|
||
const dataView = new DataView(buffer);
|
||
|
||
for (let i = 0; i < length; i++) {
|
||
const byte = parseInt(hex.substr(i * 2, 2), 16);
|
||
dataView.setUint8(i, byte);
|
||
}
|
||
|
||
return buffer;
|
||
}
|
||
|
||
/**
|
||
* 十六进制字符串转字节数组
|
||
* @param {string} hex - 十六进制字符串
|
||
* @returns {Array<number>} 字节数组
|
||
*/
|
||
function hexToBytes(hex) {
|
||
hex = hex.replace(/\s+/g, '');
|
||
const bytes = [];
|
||
for (let i = 0; i < hex.length; i += 2) {
|
||
bytes.push(parseInt(hex.substr(i, 2), 16));
|
||
}
|
||
return bytes;
|
||
}
|
||
|
||
/**
|
||
* 字节数组转十六进制字符串
|
||
* @param {Array<number>} bytes - 字节数组
|
||
* @returns {string} 十六进制字符串
|
||
*/
|
||
function bytesToHex(bytes) {
|
||
return bytes
|
||
.map(b => ('0' + b.toString(16)).slice(-2))
|
||
.join('')
|
||
.toUpperCase();
|
||
}
|
||
|
||
/**
|
||
* 十六进制字符串转 ASCII 显示文本(收发区「Hex」取消勾选时的视图)
|
||
* 不可打印字节显示为「·」
|
||
* @param {string} hex - 十六进制字符串
|
||
* @returns {string} ASCII 文本
|
||
*/
|
||
function asciiOfHex(hex) {
|
||
if (!hex) return '';
|
||
const bytes = hexToBytes(hex);
|
||
let out = '';
|
||
for (let i = 0; i < bytes.length; i++) {
|
||
const b = bytes[i];
|
||
out += (b >= 0x20 && b <= 0x7e) ? String.fromCharCode(b) : '·';
|
||
}
|
||
return out;
|
||
}
|
||
|
||
/**
|
||
* ASCII 文本转十六进制字符串(收发区「Hex」取消勾选时按文本发送)
|
||
* 含超出 Latin-1 的字符(如中文)时返回 null
|
||
* @param {string} text - ASCII 文本
|
||
* @returns {string|null} 十六进制字符串;非法字符返回 null
|
||
*/
|
||
function asciiTextToHex(text) {
|
||
if (!text) return '';
|
||
const bytes = [];
|
||
for (let i = 0; i < text.length; i++) {
|
||
const code = text.charCodeAt(i);
|
||
if (code > 0xFF) return null;
|
||
bytes.push(code);
|
||
}
|
||
return bytesToHex(bytes);
|
||
}
|
||
|
||
/**
|
||
* CRC16 校验(Modbus RTU)
|
||
* @param {string} hex - 十六进制字符串(不含 CRC)
|
||
* @returns {string} CRC16 校验码(4位十六进制)
|
||
*/
|
||
function crc16(hex) {
|
||
const bytes = hexToBytes(hex);
|
||
let crc = 0xFFFF;
|
||
|
||
for (let i = 0; i < bytes.length; i++) {
|
||
crc ^= bytes[i];
|
||
for (let j = 0; j < 8; j++) {
|
||
if (crc & 0x0001) {
|
||
crc = (crc >> 1) ^ 0xA001;
|
||
} else {
|
||
crc = crc >> 1;
|
||
}
|
||
}
|
||
}
|
||
|
||
// 返回低位在前,高位在后
|
||
const low = (crc & 0xFF).toString(16).padStart(2, '0');
|
||
const high = ((crc >> 8) & 0xFF).toString(16).padStart(2, '0');
|
||
return (low + high).toUpperCase();
|
||
}
|
||
|
||
/**
|
||
* 验证 CRC16 校验码
|
||
* @param {string} hex - 完整的十六进制字符串(含 CRC)
|
||
* @returns {boolean} 校验是否通过
|
||
*/
|
||
function verifyCrc16(hex) {
|
||
if (hex.length < 4) return false;
|
||
|
||
const data = hex.slice(0, -4);
|
||
const crc = hex.slice(-4);
|
||
|
||
return crc16(data) === crc;
|
||
}
|
||
|
||
module.exports = {
|
||
arrayBufferToHex,
|
||
hexToArrayBuffer,
|
||
hexToBytes,
|
||
bytesToHex,
|
||
asciiOfHex,
|
||
asciiTextToHex,
|
||
crc16,
|
||
verifyCrc16,
|
||
}; |