const { BYTE_ORDER_LOW, appendCrc16Modbus, hasValidCrc16Modbus } = require('./crc') const MODBUS_CRC_OPTIONS = { byteOrder: BYTE_ORDER_LOW } const MAX_MODBUS_DMA_BYTES = 64 const MODBUS_READ_RESPONSE_OVERHEAD = 5 const MODBUS_WRITE_MULTIPLE_REQUEST_OVERHEAD = 9 const MAX_READ_REGISTER_QUANTITY = Math.floor((MAX_MODBUS_DMA_BYTES - MODBUS_READ_RESPONSE_OVERHEAD) / 2) const MAX_READ_COIL_QUANTITY = (MAX_MODBUS_DMA_BYTES - MODBUS_READ_RESPONSE_OVERHEAD) * 8 const MAX_WRITE_MULTIPLE_REGISTER_QUANTITY = Math.floor((MAX_MODBUS_DMA_BYTES - MODBUS_WRITE_MULTIPLE_REQUEST_OVERHEAD) / 2) const UNLIMITED_FRAME_BYTES = 0 function toByte(value, label) { if (!Number.isInteger(value) || value < 0 || value > 0xFF) { throw new Error(`${label}必须在 0x00 至 0xFF 之间`) } return value } function toWord(value, label) { if (!Number.isInteger(value) || value < 0 || value > 0xFFFF) { throw new Error(`${label}必须在 0x0000 至 0xFFFF 之间`) } return value } function splitWord(value) { return [(value >> 8) & 0xFF, value & 0xFF] } function normalizeMaxFrameBytes(maxFrameBytes = MAX_MODBUS_DMA_BYTES) { const numberValue = Number(maxFrameBytes) if (Number.isFinite(numberValue) && Math.round(numberValue) === UNLIMITED_FRAME_BYTES) return UNLIMITED_FRAME_BYTES if (Number.isFinite(numberValue) && numberValue > 0) return Math.round(numberValue) return MAX_MODBUS_DMA_BYTES } function getMaxReadQuantity(functionCode, maxFrameBytes = MAX_MODBUS_DMA_BYTES) { const frameBytes = normalizeMaxFrameBytes(maxFrameBytes) if (frameBytes === UNLIMITED_FRAME_BYTES) return 0xFFFF const dataBytes = frameBytes - MODBUS_READ_RESPONSE_OVERHEAD if (dataBytes <= 0) return 0 if (functionCode === 0x01 || functionCode === 0x02) return dataBytes * 8 if (functionCode === 0x03 || functionCode === 0x04) return Math.floor(dataBytes / 2) return 0 } function getMaxWriteMultipleRegisterQuantity(maxFrameBytes = MAX_MODBUS_DMA_BYTES) { const frameBytes = normalizeMaxFrameBytes(maxFrameBytes) if (frameBytes === UNLIMITED_FRAME_BYTES) return 0xFFFF return Math.max(0, Math.floor((frameBytes - MODBUS_WRITE_MULTIPLE_REQUEST_OVERHEAD) / 2)) } function buildReadFrame(slaveAddress, functionCode, address, quantity, options = {}) { const slave = toByte(slaveAddress, '从站地址') const command = toByte(functionCode, '功能码') const startAddress = toWord(address, '寄存器地址') const registerQuantity = toWord(quantity, '读取数量') const maxQuantity = getMaxReadQuantity(command, options.maxFrameBytes) if (![0x01, 0x02, 0x03, 0x04].includes(command)) { throw new Error('当前功能码不是读取命令') } if (registerQuantity === 0) { throw new Error('读取数量必须大于 0') } if ([0x03, 0x04].includes(command) && maxQuantity > 0 && registerQuantity > maxQuantity) { throw new Error(`单帧最多读取 ${maxQuantity} 个寄存器`) } if ((command === 0x01 || command === 0x02) && maxQuantity > 0 && registerQuantity > maxQuantity) { throw new Error(`单帧最多读取 ${maxQuantity} 个位状态`) } return appendCrc16Modbus( [slave, command].concat(splitWord(startAddress), splitWord(registerQuantity)), MODBUS_CRC_OPTIONS ) } function buildWriteSingleCoilFrame(slaveAddress, address, checked) { const slave = toByte(slaveAddress, '从站地址') const startAddress = toWord(address, '线圈地址') const outputValue = checked ? 0xFF00 : 0x0000 return appendCrc16Modbus( [slave, 0x05].concat(splitWord(startAddress), splitWord(outputValue)), MODBUS_CRC_OPTIONS ) } function buildWriteSingleRegisterFrame(slaveAddress, address, value) { const slave = toByte(slaveAddress, '从站地址') const startAddress = toWord(address, '寄存器地址') const registerValue = toWord(value, '写入值') return appendCrc16Modbus( [slave, 0x06].concat(splitWord(startAddress), splitWord(registerValue)), MODBUS_CRC_OPTIONS ) } function buildWriteMultipleRegistersFrame(slaveAddress, address, values, options = {}) { const slave = toByte(slaveAddress, '从站地址') const startAddress = toWord(address, '寄存器地址') const maxQuantity = getMaxWriteMultipleRegisterQuantity(options.maxFrameBytes) if (!Array.isArray(values) || values.length === 0) { throw new Error('请输入至少一个寄存器写入值') } if (maxQuantity > 0 && values.length > maxQuantity) { throw new Error(`单帧最多写入 ${maxQuantity} 个寄存器`) } const registerBytes = values.reduce((result, value) => { return result.concat(splitWord(toWord(value, '写入值'))) }, []) return appendCrc16Modbus( [slave, 0x10] .concat(splitWord(startAddress), splitWord(values.length), [registerBytes.length], registerBytes), MODBUS_CRC_OPTIONS ) } function formatHex(bytes) { return bytes.map((byte) => byte.toString(16).padStart(2, '0').toUpperCase()).join(' ') } function getReadResponseByteLength(functionCode, quantity) { if (functionCode === 0x01 || functionCode === 0x02) return MODBUS_READ_RESPONSE_OVERHEAD + Math.ceil(Number(quantity || 0) / 8) if (functionCode === 0x03 || functionCode === 0x04) return MODBUS_READ_RESPONSE_OVERHEAD + Number(quantity || 0) * 2 return 0 } module.exports = { MAX_MODBUS_DMA_BYTES, MAX_READ_COIL_QUANTITY, MAX_READ_REGISTER_QUANTITY, MAX_WRITE_MULTIPLE_REGISTER_QUANTITY, MODBUS_CRC_OPTIONS, UNLIMITED_FRAME_BYTES, buildReadFrame, buildWriteMultipleRegistersFrame, buildWriteSingleCoilFrame, buildWriteSingleRegisterFrame, formatHex, getMaxWriteMultipleRegisterQuantity, getMaxReadQuantity, getReadResponseByteLength, hasValidCrc16Modbus }