1
0

code-info-parser.js 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190
  1. const {
  2. bytesToHex,
  3. trimTrailingNullBytes
  4. } = require('../../utils/binary-utils.js')
  5. const FIXED_HEADER_BYTE_LENGTH = 44
  6. const STRUCT_ENTRY_MIN_BYTE_LENGTH = 5
  7. const MEMORY_TYPE_AREAS = {
  8. 0x00: 'DATA',
  9. 0x01: 'IDATA',
  10. 0x02: 'XDATA',
  11. 0x03: 'CODE'
  12. }
  13. function toBytes(bytes) {
  14. return Array.prototype.slice.call(bytes || []).map((byte) => Number(byte) & 0xFF)
  15. }
  16. function readUint16(bytes, offset) {
  17. if (offset + 1 >= bytes.length) return 0
  18. return (((bytes[offset] || 0) << 8) | (bytes[offset + 1] || 0)) & 0xFFFF
  19. }
  20. function readFloat(bytes, offset) {
  21. if (offset + 3 >= bytes.length) return 0
  22. const buffer = new ArrayBuffer(4)
  23. const view = new DataView(buffer)
  24. for (let index = 0; index < 4; index += 1) {
  25. view.setUint8(index, bytes[offset + index] || 0)
  26. }
  27. return view.getFloat32(0, false)
  28. }
  29. function readAscii(bytes, offset, byteLength) {
  30. const source = trimTrailingNullBytes(bytes.slice(offset, offset + byteLength))
  31. return source
  32. .map((byte) => (byte >= 0x20 && byte <= 0x7E ? String.fromCharCode(byte) : ''))
  33. .join('')
  34. .trim()
  35. }
  36. function formatAddress(address) {
  37. return `0x${Number(address || 0).toString(16).toUpperCase().padStart(4, '0')}`
  38. }
  39. function normalizeTypeName(value, fallback) {
  40. const text = String(value || '').trim()
  41. return text || fallback
  42. }
  43. function parseStructEntry(bytes, offset, entryLength, index) {
  44. const byteAddr = readUint16(bytes, offset)
  45. const byteLength = readUint16(bytes, offset + 2)
  46. const memType = bytes[offset + 4] & 0xFF
  47. const nameByteLength = Math.max(0, entryLength - STRUCT_ENTRY_MIN_BYTE_LENGTH)
  48. const typeName = normalizeTypeName(
  49. readAscii(bytes, offset + STRUCT_ENTRY_MIN_BYTE_LENGTH, nameByteLength),
  50. `struct_${index + 1}`
  51. )
  52. const memoryArea = MEMORY_TYPE_AREAS[memType] || 'UNKNOWN'
  53. return {
  54. byteAddr,
  55. byteLength,
  56. index,
  57. memType,
  58. memoryArea,
  59. sourceAddressText: formatAddress(byteAddr),
  60. typeName
  61. }
  62. }
  63. function parseModbusCodeInfo(bytes) {
  64. const source = toBytes(bytes)
  65. if (source.length < FIXED_HEADER_BYTE_LENGTH) {
  66. throw new Error('Code信息块长度不足,无法解析 Modbus_Code_Info_t 固定头')
  67. }
  68. const byteLength = readUint16(source, 0)
  69. const structCount = readUint16(source, 40)
  70. const structEntryLength = readUint16(source, 42)
  71. if (structEntryLength < STRUCT_ENTRY_MIN_BYTE_LENGTH) {
  72. throw new Error('Code信息块 struct_entry_len 无效')
  73. }
  74. const availableTableBytes = Math.max(0, source.length - FIXED_HEADER_BYTE_LENGTH)
  75. const tableCapacity = Math.floor(availableTableBytes / structEntryLength)
  76. const safeStructCount = Math.min(structCount, tableCapacity)
  77. const structTable = []
  78. for (let index = 0; index < safeStructCount; index += 1) {
  79. structTable.push(parseStructEntry(
  80. source,
  81. FIXED_HEADER_BYTE_LENGTH + index * structEntryLength,
  82. structEntryLength,
  83. index
  84. ))
  85. }
  86. return {
  87. alongDiv: readFloat(source, 12),
  88. ampGain: readUint16(source, 4),
  89. busDiv: readFloat(source, 8),
  90. byteLength,
  91. caveFreq: source[2] & 0xFF,
  92. chipModel: readAscii(source, 16, 8),
  93. model: readAscii(source, 24, 16),
  94. rawHex: bytesToHex(source, ' '),
  95. refVolt: source[3] & 0xFF,
  96. rsShunt: readUint16(source, 6),
  97. structCount,
  98. structEntryLength,
  99. structTable,
  100. tableCapacity,
  101. truncated: safeStructCount < structCount
  102. }
  103. }
  104. function createRegistersForByteSpan(entry) {
  105. const count = Math.max(1, Number(entry.byteLength) || 1)
  106. const registers = []
  107. for (let offset = 0; offset < count; offset += 1) {
  108. registers.push({
  109. byteStart: offset,
  110. dataType: 'uint8_t',
  111. name: `${entry.typeName}[${offset.toString(16).toUpperCase().padStart(2, '0')}]`,
  112. remark: `${entry.memoryArea} ${formatAddress(entry.byteAddr + offset)} · ${entry.typeName}`,
  113. sourceAddress: (entry.byteAddr + offset) & 0xFFFF,
  114. sourceAddressText: formatAddress((entry.byteAddr + offset) & 0xFFFF),
  115. sourceByteLength: 1,
  116. sourceMemoryArea: entry.memoryArea,
  117. sourceMemoryClass: entry.memoryArea,
  118. sourceSymbolName: entry.typeName,
  119. sourceSymbolType: entry.typeName
  120. })
  121. }
  122. return registers
  123. }
  124. function createGroupsFromCodeInfo(codeInfo, options = {}) {
  125. const maxRegisters = Math.max(1, Number(options.maxRegistersPerGroup) || 256)
  126. const groups = []
  127. codeInfo.structTable.forEach((entry) => {
  128. if (!entry.byteLength || entry.memoryArea === 'UNKNOWN') return
  129. for (let offset = 0; offset < entry.byteLength; offset += maxRegisters) {
  130. const chunkLength = Math.min(maxRegisters, entry.byteLength - offset)
  131. const chunkEntry = {
  132. ...entry,
  133. byteAddr: (entry.byteAddr + offset) & 0xFFFF,
  134. byteLength: chunkLength
  135. }
  136. const suffix = entry.byteLength > maxRegisters ? ` #${Math.floor(offset / maxRegisters) + 1}` : ''
  137. groups.push({
  138. addressUnit: 'byte',
  139. layout: 'struct',
  140. name: `${entry.memoryArea} ${entry.typeName}${suffix}`,
  141. quantity: chunkLength,
  142. registerType: entry.memoryArea === 'CODE' ? 'input' : 'holding',
  143. registers: createRegistersForByteSpan(chunkEntry),
  144. sourceAddress: chunkEntry.byteAddr,
  145. sourceAddressText: formatAddress(chunkEntry.byteAddr),
  146. sourceByteLength: chunkLength,
  147. sourceMemoryArea: entry.memoryArea,
  148. sourceMemoryClass: entry.memoryArea,
  149. sourceSegment: 'Modbus_Code_Info_t',
  150. sourceSegmentModule: '',
  151. sourceSymbolName: entry.typeName,
  152. startAddress: formatAddress(chunkEntry.byteAddr)
  153. })
  154. }
  155. })
  156. return groups
  157. }
  158. module.exports = {
  159. FIXED_HEADER_BYTE_LENGTH,
  160. createGroupsFromCodeInfo,
  161. parseModbusCodeInfo
  162. }