modbus-connect
Version:
Modbus RTU over Web Serial and Node.js SerialPort
106 lines (90 loc) • 4.27 kB
JavaScript
// function-codes/SGM130/read-device-comment.js
const FUNCTION_CODE = 0x14;
const MAX_CHANNEL = 255;
const MAX_COMMENT_LENGTH = 16;
const REQUEST_SIZE = 2;
const RESPONSE_HEADER_SIZE = 3;
// Предварительно инициализированные кэши
const textDecoder = new TextDecoder('windows-1251');
const SYMBOL_TABLE = new Uint8Array(256).fill(32); // Заполняем пробелами (ASCII 32)
// Инициализация таблицы символов (выполняется один раз)
(function initSymbolTable() {
// Цифры и латинские буквы
const symbols = ' 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ';
for (let i = 0; i < symbols.length; i++) {
SYMBOL_TABLE[i] = symbols.charCodeAt(i);
}
// Русские буквы (CP1251 кодировка)
const cyrillic = 'АБВГДЕЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯ';
for (let i = 0; i < cyrillic.length; i++) {
SYMBOL_TABLE[37 + i] = cyrillic.charCodeAt(i);
}
})();
/**
* Формирует запрос на чтение комментария устройства
* @param {number} channel - Номер канала (0-255)
* @returns {Uint8Array} - PDU запроса (2 байта)
* @throws {RangeError} При неверном номере канала
*/
function buildReadDeviceCommentRequest(channel) {
// Быстрая проверка через побитовые операции
if ((channel | 0) !== channel || channel < 0 || channel > MAX_CHANNEL) {
throw new RangeError(`Channel must be 0-${MAX_CHANNEL}`);
}
// Используем статический массив для исключения лишних аллокаций
return new Uint8Array([FUNCTION_CODE, channel]);
}
/**
* Разбирает ответ с комментарием устройства
* @param {Uint8Array} pdu - PDU ответа
* @returns {{
* channel: number,
* raw: Uint8Array,
* comment: string
* }}
* @throws {TypeError|Error} При неверном формате данных
*/
function parseReadDeviceCommentResponse(pdu) {
// Строгая проверка типа (без try/catch)
if (!(pdu?.constructor === Uint8Array)) {
throw new TypeError(`Expected Uint8Array, got ${pdu?.constructor?.name || typeof pdu}`);
}
const pduLength = pdu.length;
if (pduLength < RESPONSE_HEADER_SIZE) {
throw new Error(`PDU too short: expected at least ${RESPONSE_HEADER_SIZE} bytes, got ${pduLength}`);
}
// Проверка кода функции
if (pdu[0] !== FUNCTION_CODE) {
const receivedCode = pdu[0]?.toString(16).padStart(2, '0') || 'null';
throw new Error(`Invalid function code: expected 0x${FUNCTION_CODE.toString(16)}, got 0x${receivedCode}`);
}
const channel = pdu[1];
const length = pdu[2];
// Проверка длины комментария
if (length > MAX_COMMENT_LENGTH) {
throw new Error(`Comment length exceeds limit: max ${MAX_COMMENT_LENGTH}, got ${length}`);
}
// Проверка общего размера PDU
const expectedLength = RESPONSE_HEADER_SIZE + length;
if (pduLength < expectedLength) {
throw new Error(`PDU truncated: expected ${expectedLength} bytes, got ${pduLength}`);
}
// Оптимизированное чтение данных
const buffer = pdu.buffer || pdu;
const byteOffset = (pdu.byteOffset || 0) + RESPONSE_HEADER_SIZE;
const rawData = new Uint8Array(buffer, byteOffset, length);
const commentBuffer = new Uint8Array(length);
// Преобразование символов через предзаполненную таблицу
for (let i = 0; i < length; i++) {
commentBuffer[i] = SYMBOL_TABLE[rawData[i]] || 32;
}
return {
channel,
raw: rawData, // Возвращаем Uint8Array вместо Array для производительности
comment: textDecoder.decode(commentBuffer)
};
}
module.exports = {
buildReadDeviceCommentRequest,
parseReadDeviceCommentResponse,
};