frame-builder.js 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165
  1. const {
  2. buildReadFrame,
  3. buildWriteMultipleRegistersFrame,
  4. buildWriteSingleCoilFrame,
  5. buildWriteSingleRegisterFrame,
  6. formatHex
  7. } = require('../../protocols/modbus-rtu/index.js')
  8. const MODBUS_COMMANDS = [
  9. { key: 'readCoils', label: '01 读取线圈', functionCode: 0x01, inputMode: 'quantity' },
  10. { key: 'readDiscreteInputs', label: '02 读取离散输入', functionCode: 0x02, inputMode: 'quantity' },
  11. { key: 'readHolding', label: '03 读取保持寄存器', functionCode: 0x03, inputMode: 'quantity' },
  12. { key: 'readInput', label: '04 读取输入寄存器', functionCode: 0x04, inputMode: 'quantity' },
  13. { key: 'writeCoil', label: '05 写单线圈', functionCode: 0x05, inputMode: 'coil' },
  14. { key: 'writeRegister', label: '06 写单寄存器', functionCode: 0x06, inputMode: 'single' },
  15. { key: 'writeRegisters', label: '10 写多寄存器', functionCode: 0x10, inputMode: 'multiple' }
  16. ]
  17. function parseHexNumber(value, label, maxValue) {
  18. const text = String(value || '').trim().replace(/^0x/i, '')
  19. if (!text || !/^[0-9a-fA-F]+$/.test(text)) {
  20. throw new Error(`${label}请输入十六进制数值`)
  21. }
  22. const parsedValue = parseInt(text, 16)
  23. if (parsedValue > maxValue) {
  24. throw new Error(`${label}超出范围`)
  25. }
  26. return parsedValue
  27. }
  28. function parseRegisterValues(value) {
  29. const text = String(value || '').trim()
  30. if (!text) throw new Error('请输入寄存器写入值')
  31. return text.split(/[\s,;]+/)
  32. .filter(Boolean)
  33. .map((item) => parseHexNumber(item, '写入值', 0xFFFF))
  34. }
  35. function getCommand(index) {
  36. return MODBUS_COMMANDS[index] || MODBUS_COMMANDS[0]
  37. }
  38. function getDefaultCommandValue(command) {
  39. if (command.inputMode === 'quantity') return '0001'
  40. if (command.inputMode === 'coil') return 'ON'
  41. if (command.inputMode === 'multiple') return '0000'
  42. return '0000'
  43. }
  44. function generateModbusFrame(command, slaveAddress, registerAddress, commandValue, coilEnabled, commandRegisterQuantity) {
  45. const slave = parseHexNumber(slaveAddress, '从站地址', 0xFF)
  46. const address = parseHexNumber(registerAddress, '起始地址', 0xFFFF)
  47. if (command.inputMode === 'quantity') {
  48. const quantity = parseHexNumber(commandValue, '读取数量', 0xFFFF)
  49. return buildReadFrame(slave, command.functionCode, address, quantity)
  50. }
  51. if (command.inputMode === 'coil') {
  52. return buildWriteSingleCoilFrame(slave, address, coilEnabled)
  53. }
  54. if (command.inputMode === 'single') {
  55. return buildWriteSingleRegisterFrame(slave, address, parseHexNumber(commandValue, '写入值', 0xFFFF))
  56. }
  57. const words = parseRegisterValues(commandValue)
  58. const quantity = parseHexNumber(commandRegisterQuantity, '寄存器个数', 0xFFFF)
  59. if (quantity !== words.length) {
  60. throw new Error(`写入值数量应为 ${quantity} 个寄存器`)
  61. }
  62. return buildWriteMultipleRegistersFrame(slave, address, words)
  63. }
  64. function createProtocolState(commandIndex, slaveAddress, registerAddress, commandValue, coilEnabled, commandRegisterQuantity) {
  65. const command = getCommand(commandIndex)
  66. const commandValueLabel = command.inputMode === 'quantity' ? '读取数量' : '写入值'
  67. try {
  68. return {
  69. commandValueLabel,
  70. generatedHex: formatHex(generateModbusFrame(command, slaveAddress, registerAddress, commandValue, coilEnabled, commandRegisterQuantity)),
  71. protocolErrorText: '',
  72. showCoilValue: command.inputMode === 'coil',
  73. showRegisterQuantity: command.inputMode === 'multiple',
  74. showCommandValue: command.inputMode !== 'coil'
  75. }
  76. } catch (error) {
  77. return {
  78. commandValueLabel,
  79. generatedHex: '',
  80. protocolErrorText: error.message,
  81. showCoilValue: command.inputMode === 'coil',
  82. showRegisterQuantity: command.inputMode === 'multiple',
  83. showCommandValue: command.inputMode !== 'coil'
  84. }
  85. }
  86. }
  87. function buildGeneratedExpectedResponse(state = {}) {
  88. try {
  89. const command = getCommand(state.commandIndex)
  90. const address = parseHexNumber(state.registerAddress, '起始地址', 0xFFFF)
  91. const slaveAddress = parseHexNumber(state.slaveAddress, '从站地址', 0xFF)
  92. const quantity = command.inputMode === 'quantity'
  93. ? parseHexNumber(state.commandValue, '读取数量', 0xFFFF)
  94. : (command.inputMode === 'multiple' ? parseHexNumber(state.commandRegisterQuantity, '寄存器个数', 0xFFFF) : 1)
  95. const value = command.inputMode === 'coil'
  96. ? (state.coilEnabled ? 0xFF00 : 0x0000)
  97. : (command.inputMode === 'single' ? parseHexNumber(state.commandValue, '写入值', 0xFFFF) : undefined)
  98. return {
  99. address,
  100. functionCode: command.functionCode,
  101. kind: 'manual-rtu',
  102. protocol: 'modbus-rtu',
  103. quantity,
  104. value,
  105. slaveAddress
  106. }
  107. } catch (error) {
  108. return null
  109. }
  110. }
  111. function formatResponseHex(value, width) {
  112. return Number(value || 0).toString(16).toUpperCase().padStart(width, '0')
  113. }
  114. function formatGeneratedResponse(response) {
  115. if (!response) return ''
  116. const slave = formatResponseHex(response.slaveAddress, 2)
  117. const functionCode = formatResponseHex(response.functionCode, 2)
  118. if (Array.isArray(response.words) && response.words.length) {
  119. return `从机 0x${slave} / 功能码 0x${functionCode} / 字 ${response.words.map((word) => formatResponseHex(word, 4)).join(' ')}`
  120. }
  121. if (Array.isArray(response.dataBytes) && response.dataBytes.length) {
  122. return `从机 0x${slave} / 功能码 0x${functionCode} / 数据 ${formatHex(response.dataBytes)}`
  123. }
  124. if (Number.isInteger(response.address)) {
  125. return `从机 0x${slave} / 功能码 0x${functionCode} / 地址 0x${formatResponseHex(response.address, 4)} / 值 0x${formatResponseHex(response.quantityOrValue, 4)}`
  126. }
  127. return `从机 0x${slave} / 功能码 0x${functionCode}`
  128. }
  129. module.exports = {
  130. MODBUS_COMMANDS,
  131. buildGeneratedExpectedResponse,
  132. createProtocolState,
  133. formatGeneratedResponse,
  134. generateModbusFrame,
  135. getCommand,
  136. getDefaultCommandValue,
  137. parseHexNumber,
  138. parseRegisterValues
  139. }