const { MAX_MODBUS_DMA_BYTES, MODBUS_CRC_OPTIONS, getReadResponseByteLength, hasValidCrc16Modbus } = require('./frame.js') const { padHex } = require('../../utils/base-utils.js') const { bytesToWords } = require('../../utils/binary-utils.js') const MODBUS_EXCEPTION_MESSAGES = { 0x01: '非法功能', 0x02: '非法数据地址', 0x03: '非法数据值', 0x04: '从站设备故障', 0x05: '确认', 0x06: '从站设备忙', 0x08: '存储奇偶性错误', 0x0A: '网关路径不可用', 0x0B: '网关目标设备响应失败' } const UNLIMITED_FRAME_BYTES = 0 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 parseModbusResponse(bytes) { if (!Array.isArray(bytes) || bytes.length < 5 || !hasValidCrc16Modbus(bytes, MODBUS_CRC_OPTIONS)) return null const slaveAddress = bytes[0] const functionCode = bytes[1] if (functionCode & 0x80) { return { exceptionCode: bytes[2], functionCode, isException: true, slaveAddress, sourceFunctionCode: functionCode & 0x7F } } if (functionCode === 0x01 || functionCode === 0x02) { const byteCount = bytes[2] const dataEnd = 3 + byteCount if (bytes.length < dataEnd + 2) return null return { byteCount, dataBytes: bytes.slice(3, dataEnd), functionCode, isException: false, slaveAddress } } if (functionCode === 0x03 || functionCode === 0x04) { const byteCount = bytes[2] const dataEnd = 3 + byteCount if (bytes.length < dataEnd + 2) return null return { byteCount, dataBytes: bytes.slice(3, dataEnd), functionCode, isException: false, slaveAddress, words: bytesToWords(bytes.slice(3, dataEnd)) } } if (functionCode === 0x05 || functionCode === 0x06 || functionCode === 0x10) { return { address: ((bytes[2] << 8) | bytes[3]) & 0xFFFF, functionCode, isException: false, quantityOrValue: ((bytes[4] << 8) | bytes[5]) & 0xFFFF, slaveAddress } } return { functionCode, isException: false, slaveAddress } } function parseModbusRequest(bytes) { if (!Array.isArray(bytes) || bytes.length < 6 || !hasValidCrc16Modbus(bytes, MODBUS_CRC_OPTIONS)) return null const slaveAddress = bytes[0] const functionCode = bytes[1] const address = ((bytes[2] << 8) | bytes[3]) & 0xFFFF let quantity = 1 let value if (functionCode === 0x01 || functionCode === 0x02 || functionCode === 0x03 || functionCode === 0x04 || functionCode === 0x10) { quantity = ((bytes[4] << 8) | bytes[5]) & 0xFFFF } if (functionCode === 0x05 || functionCode === 0x06) { value = ((bytes[4] << 8) | bytes[5]) & 0xFFFF } return { address, functionCode, kind: 'raw-hex', quantity, value, slaveAddress } } function getExpectedResponseLength(expected, responseFunctionCode, responseBytes = []) { if (!expected) return 0 if (responseFunctionCode === (expected.functionCode | 0x80)) { return 5 } if (responseFunctionCode === 0x01 || responseFunctionCode === 0x02) { if (responseBytes.length < 3) return 0 return 3 + Number(responseBytes[2] || 0) + 2 } if (responseFunctionCode === 0x03 || responseFunctionCode === 0x04) { if (responseBytes.length < 3) return 0 return 3 + Number(responseBytes[2] || 0) + 2 } if (responseFunctionCode === 0x05 || responseFunctionCode === 0x06 || responseFunctionCode === 0x10) { return 8 } return 0 } function isExpectedResponse(response, expected) { if (response.functionCode === 0x01 || response.functionCode === 0x02) { return Array.isArray(response.dataBytes) && response.dataBytes.length >= Math.ceil(expected.quantity / 8) } if (response.functionCode === 0x03 || response.functionCode === 0x04) { return Array.isArray(response.words) && response.words.length >= expected.quantity } if (response.functionCode === 0x10) { return response.address === expected.address && response.quantityOrValue === expected.quantity } if (response.functionCode === 0x05 || response.functionCode === 0x06) { if (response.address !== expected.address) return false if (Number.isInteger(expected.value)) return response.quantityOrValue === expected.value return true } return true } function getExceptionText(code) { return MODBUS_EXCEPTION_MESSAGES[code] || '未知异常' } function formatExceptionMessage(response) { const sourceFunctionCode = response && response.sourceFunctionCode const exceptionCode = response && response.exceptionCode const exceptionText = getExceptionText(exceptionCode) return `设备返回异常帧:功能码 0x${padHex(sourceFunctionCode, 2)},异常码 0x${padHex(exceptionCode, 2)}(${exceptionText})` } function getReadBufferHint(expected) { return expected ? getReadResponseByteLength(expected.functionCode, expected.quantity) : 0 } function alignResponseBuffer(buffer, expected) { if (!Array.isArray(buffer) || !buffer.length || !expected) return const expectedFunctionCodes = [expected.functionCode, expected.functionCode | 0x80] let matchIndex = -1 for (let index = 0; index < buffer.length - 1; index += 1) { if (buffer[index] !== expected.slaveAddress) continue if (!expectedFunctionCodes.includes(buffer[index + 1])) continue matchIndex = index break } if (matchIndex > 0) { buffer.splice(0, matchIndex) } else if (matchIndex < 0 && buffer.length > 2) { buffer.splice(0, buffer.length - 1) } } function readResponseFromBuffer(buffer, expected, options = {}) { if (!Array.isArray(buffer) || !buffer.length || !expected) { return { status: 'pending' } } alignResponseBuffer(buffer, expected) while (buffer.length >= 2) { const responseFunctionCode = buffer[1] const responseLength = getExpectedResponseLength(expected, responseFunctionCode, buffer) if (!responseLength) { return { status: 'pending' } } const frameLimit = normalizeMaxFrameBytes( options.maxFrameBytes === undefined ? expected.maxFrameBytes : options.maxFrameBytes ) if (frameLimit > 0 && responseLength > frameLimit) { return { frameLimit, responseLength, status: 'frame-too-long' } } if (buffer.length < responseLength) { return { status: 'pending' } } const frameBytes = buffer.slice(0, responseLength) const response = parseModbusResponse(frameBytes) if (!response) { return { frameBytes, status: 'invalid' } } const responseCode = response.isException ? response.sourceFunctionCode : response.functionCode if (response.slaveAddress !== expected.slaveAddress || responseCode !== expected.functionCode) { buffer.shift() alignResponseBuffer(buffer, expected) continue } if (response.isException) { return { message: formatExceptionMessage(response), response, status: 'exception' } } if (!isExpectedResponse(response, expected)) { return { response, status: 'mismatch' } } buffer.splice(0, responseLength) return { response, status: 'complete' } } return { status: 'pending' } } module.exports = { MODBUS_EXCEPTION_MESSAGES, formatExceptionMessage, getExceptionText, getExpectedResponseLength, getReadBufferHint, isExpectedResponse, parseModbusRequest, parseModbusResponse, readResponseFromBuffer }