const { bytesToHex, trimTrailingNullBytes } = require('../../utils/binary-utils.js') const FIXED_HEADER_BYTE_LENGTH = 44 const STRUCT_ENTRY_MIN_BYTE_LENGTH = 5 const MEMORY_TYPE_AREAS = { 0x00: 'DATA', 0x01: 'IDATA', 0x02: 'XDATA', 0x03: 'CODE' } function toBytes(bytes) { return Array.prototype.slice.call(bytes || []).map((byte) => Number(byte) & 0xFF) } function readUint16(bytes, offset) { if (offset + 1 >= bytes.length) return 0 return (((bytes[offset] || 0) << 8) | (bytes[offset + 1] || 0)) & 0xFFFF } function readFloat(bytes, offset) { if (offset + 3 >= bytes.length) return 0 const buffer = new ArrayBuffer(4) const view = new DataView(buffer) for (let index = 0; index < 4; index += 1) { view.setUint8(index, bytes[offset + index] || 0) } return view.getFloat32(0, false) } function readAscii(bytes, offset, byteLength) { const source = trimTrailingNullBytes(bytes.slice(offset, offset + byteLength)) return source .map((byte) => (byte >= 0x20 && byte <= 0x7E ? String.fromCharCode(byte) : '')) .join('') .trim() } function formatAddress(address) { return `0x${Number(address || 0).toString(16).toUpperCase().padStart(4, '0')}` } function normalizeTypeName(value, fallback) { const text = String(value || '').trim() return text || fallback } function parseStructEntry(bytes, offset, entryLength, index) { const byteAddr = readUint16(bytes, offset) const byteLength = readUint16(bytes, offset + 2) const memType = bytes[offset + 4] & 0xFF const nameByteLength = Math.max(0, entryLength - STRUCT_ENTRY_MIN_BYTE_LENGTH) const typeName = normalizeTypeName( readAscii(bytes, offset + STRUCT_ENTRY_MIN_BYTE_LENGTH, nameByteLength), `struct_${index + 1}` ) const memoryArea = MEMORY_TYPE_AREAS[memType] || 'UNKNOWN' return { byteAddr, byteLength, index, memType, memoryArea, sourceAddressText: formatAddress(byteAddr), typeName } } function parseModbusCodeInfo(bytes) { const source = toBytes(bytes) if (source.length < FIXED_HEADER_BYTE_LENGTH) { throw new Error('Code信息块长度不足,无法解析 Modbus_Code_Info_t 固定头') } const byteLength = readUint16(source, 0) const structCount = readUint16(source, 40) const structEntryLength = readUint16(source, 42) if (structEntryLength < STRUCT_ENTRY_MIN_BYTE_LENGTH) { throw new Error('Code信息块 struct_entry_len 无效') } const availableTableBytes = Math.max(0, source.length - FIXED_HEADER_BYTE_LENGTH) const tableCapacity = Math.floor(availableTableBytes / structEntryLength) const safeStructCount = Math.min(structCount, tableCapacity) const structTable = [] for (let index = 0; index < safeStructCount; index += 1) { structTable.push(parseStructEntry( source, FIXED_HEADER_BYTE_LENGTH + index * structEntryLength, structEntryLength, index )) } return { alongDiv: readFloat(source, 12), ampGain: readUint16(source, 4), busDiv: readFloat(source, 8), byteLength, caveFreq: source[2] & 0xFF, chipModel: readAscii(source, 16, 8), model: readAscii(source, 24, 16), rawHex: bytesToHex(source, ' '), refVolt: source[3] & 0xFF, rsShunt: readUint16(source, 6), structCount, structEntryLength, structTable, tableCapacity, truncated: safeStructCount < structCount } } function createRegistersForByteSpan(entry) { const count = Math.max(1, Number(entry.byteLength) || 1) const registers = [] for (let offset = 0; offset < count; offset += 1) { registers.push({ byteStart: offset, dataType: 'uint8_t', name: `${entry.typeName}[${offset.toString(16).toUpperCase().padStart(2, '0')}]`, remark: `${entry.memoryArea} ${formatAddress(entry.byteAddr + offset)} · ${entry.typeName}`, sourceAddress: (entry.byteAddr + offset) & 0xFFFF, sourceAddressText: formatAddress((entry.byteAddr + offset) & 0xFFFF), sourceByteLength: 1, sourceMemoryArea: entry.memoryArea, sourceMemoryClass: entry.memoryArea, sourceSymbolName: entry.typeName, sourceSymbolType: entry.typeName }) } return registers } function createGroupsFromCodeInfo(codeInfo, options = {}) { const maxRegisters = Math.max(1, Number(options.maxRegistersPerGroup) || 256) const groups = [] codeInfo.structTable.forEach((entry) => { if (!entry.byteLength || entry.memoryArea === 'UNKNOWN') return for (let offset = 0; offset < entry.byteLength; offset += maxRegisters) { const chunkLength = Math.min(maxRegisters, entry.byteLength - offset) const chunkEntry = { ...entry, byteAddr: (entry.byteAddr + offset) & 0xFFFF, byteLength: chunkLength } const suffix = entry.byteLength > maxRegisters ? ` #${Math.floor(offset / maxRegisters) + 1}` : '' groups.push({ addressUnit: 'byte', layout: 'struct', name: `${entry.memoryArea} ${entry.typeName}${suffix}`, quantity: chunkLength, registerType: entry.memoryArea === 'CODE' ? 'input' : 'holding', registers: createRegistersForByteSpan(chunkEntry), sourceAddress: chunkEntry.byteAddr, sourceAddressText: formatAddress(chunkEntry.byteAddr), sourceByteLength: chunkLength, sourceMemoryArea: entry.memoryArea, sourceMemoryClass: entry.memoryArea, sourceSegment: 'Modbus_Code_Info_t', sourceSegmentModule: '', sourceSymbolName: entry.typeName, startAddress: formatAddress(chunkEntry.byteAddr) }) } }) return groups } module.exports = { FIXED_HEADER_BYTE_LENGTH, createGroupsFromCodeInfo, parseModbusCodeInfo }