| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190 |
- 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
- }
|