modbus-connect
Version:
Modbus RTU over Web Serial and Node.js SerialPort
100 lines (84 loc) • 3.98 kB
JavaScript
// function-codes/SGM130/write-device-comment.js
const FUNCTION_CODE = 0x15;
const MAX_CHANNEL = 255;
const MAX_COMMENT_LENGTH = 16;
const REQUEST_SIZE = 19; // 3 байта заголовка + 16 данных
const RESPONSE_SIZE = 3;
const SPACE_CODE = 0;
// Оптимизированная таблица символов как Map (быстрый поиск)
const CHAR_CODE_MAP = new Map();
[
[' ', 0], ['0', 1], ['1', 2], ['2', 3], ['3', 4], ['4', 5], ['5', 6], ['6', 7], ['7', 8], ['8', 9], ['9', 10],
['A', 11], ['B', 12], ['C', 13], ['D', 14], ['E', 15], ['F', 16], ['G', 17], ['H', 18], ['I', 19], ['J', 20],
['K', 21], ['L', 22], ['M', 23], ['N', 24], ['O', 25], ['P', 26], ['Q', 27], ['R', 28], ['S', 29], ['T', 30],
['U', 31], ['V', 32], ['X', 33], ['Y', 34], ['Z', 35],
['А', 37], ['Б', 38], ['В', 39], ['Г', 40], ['Д', 41], ['Е', 42], ['Ж', 43], ['З', 44], ['И', 45], ['Й', 46],
['К', 47], ['Л', 48], ['М', 49], ['Н', 50], ['О', 51], ['П', 52], ['Р', 53], ['С', 54], ['Т', 55], ['У', 56],
['Ф', 57], ['Х', 58], ['Ц', 59], ['Ч', 60], ['Ш', 61], ['Щ', 62], ['Ъ', 63], ['Ы', 64], ['Ь', 65], ['Э', 66],
['Ю', 67], ['Я', 68]
].forEach(([char, code]) => CHAR_CODE_MAP.set(char, code));
/**
* Создаёт PDU-запрос для записи комментария устройства
* @param {number} channel - номер канала (0-255)
* @param {string} comment - строка комментария (макс. 16 символов)
* @returns {Uint8Array}
* @throws {Error} При невалидных параметрах
*/
function buildWriteDeviceCommentRequest(channel, comment) {
// Быстрая проверка канала
if ((channel | 0) !== channel || channel < 0 || channel > MAX_CHANNEL) {
throw new RangeError(`Channel must be 0-${MAX_CHANNEL}`);
}
if (typeof comment !== 'string') {
throw new TypeError('Comment must be a string');
}
const trimmed = comment.trim().toUpperCase();
if (trimmed.length > MAX_COMMENT_LENGTH) {
throw new Error(`Comment too long: max ${MAX_COMMENT_LENGTH} chars`);
}
// Создаем буфер сразу нужного размера
const pdu = new Uint8Array(REQUEST_SIZE);
const view = new DataView(pdu.buffer);
// Заполняем заголовок
view.setUint8(0, FUNCTION_CODE);
view.setUint8(1, channel);
view.setUint8(2, MAX_COMMENT_LENGTH);
// Кодируем символы и заполняем данные
let offset = 3;
for (const ch of trimmed) {
const code = CHAR_CODE_MAP.get(ch);
if (code === undefined) {
throw new Error(`Unsupported character: "${ch}"`);
}
pdu[offset++] = code;
}
// Заполняем остаток пробелами
while (offset < REQUEST_SIZE) {
pdu[offset++] = SPACE_CODE;
}
return pdu;
}
/**
* Парсит ответ устройства на запись комментария
* @param {Uint8Array} pdu - PDU ответа
* @returns {{ channel: number, length: number }}
* @throws {Error} При невалидном ответе
*/
function parseWriteDeviceCommentResponse(pdu) {
if (!(pdu instanceof Uint8Array)) {
throw new TypeError('PDU must be Uint8Array');
}
if (pdu.length !== RESPONSE_SIZE || pdu[0] !== FUNCTION_CODE) {
throw new Error(`Invalid response: expected ${RESPONSE_SIZE} bytes starting with 0x${FUNCTION_CODE.toString(16)}`);
}
const channel = pdu[1];
const length = pdu[2];
if (length !== MAX_COMMENT_LENGTH) {
throw new Error(`Invalid length: expected ${MAX_COMMENT_LENGTH}, got ${length}`);
}
return { channel, length };
}
module.exports = {
buildWriteDeviceCommentRequest,
parseWriteDeviceCommentResponse
};