1
0

modbus-rtu.js 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163
  1. const {
  2. BYTE_ORDER_LOW,
  3. appendCrc16Modbus,
  4. hasValidCrc16Modbus
  5. } = require('./crc')
  6. const MODBUS_CRC_OPTIONS = {
  7. byteOrder: BYTE_ORDER_LOW
  8. }
  9. const MAX_MODBUS_DMA_BYTES = 64
  10. const MODBUS_READ_RESPONSE_OVERHEAD = 5
  11. const MODBUS_WRITE_MULTIPLE_REQUEST_OVERHEAD = 9
  12. const MAX_READ_REGISTER_QUANTITY = Math.floor((MAX_MODBUS_DMA_BYTES - MODBUS_READ_RESPONSE_OVERHEAD) / 2)
  13. const MAX_READ_COIL_QUANTITY = (MAX_MODBUS_DMA_BYTES - MODBUS_READ_RESPONSE_OVERHEAD) * 8
  14. const MAX_WRITE_MULTIPLE_REGISTER_QUANTITY = Math.floor((MAX_MODBUS_DMA_BYTES - MODBUS_WRITE_MULTIPLE_REQUEST_OVERHEAD) / 2)
  15. const UNLIMITED_FRAME_BYTES = 0
  16. function toByte(value, label) {
  17. if (!Number.isInteger(value) || value < 0 || value > 0xFF) {
  18. throw new Error(`${label}必须在 0x00 至 0xFF 之间`)
  19. }
  20. return value
  21. }
  22. function toWord(value, label) {
  23. if (!Number.isInteger(value) || value < 0 || value > 0xFFFF) {
  24. throw new Error(`${label}必须在 0x0000 至 0xFFFF 之间`)
  25. }
  26. return value
  27. }
  28. function splitWord(value) {
  29. return [(value >> 8) & 0xFF, value & 0xFF]
  30. }
  31. function normalizeMaxFrameBytes(maxFrameBytes = MAX_MODBUS_DMA_BYTES) {
  32. const numberValue = Number(maxFrameBytes)
  33. if (Number.isFinite(numberValue) && Math.round(numberValue) === UNLIMITED_FRAME_BYTES) return UNLIMITED_FRAME_BYTES
  34. if (Number.isFinite(numberValue) && numberValue > 0) return Math.round(numberValue)
  35. return MAX_MODBUS_DMA_BYTES
  36. }
  37. function getMaxReadQuantity(functionCode, maxFrameBytes = MAX_MODBUS_DMA_BYTES) {
  38. const frameBytes = normalizeMaxFrameBytes(maxFrameBytes)
  39. if (frameBytes === UNLIMITED_FRAME_BYTES) return 0xFFFF
  40. const dataBytes = frameBytes - MODBUS_READ_RESPONSE_OVERHEAD
  41. if (dataBytes <= 0) return 0
  42. if (functionCode === 0x01 || functionCode === 0x02) return dataBytes * 8
  43. if (functionCode === 0x03 || functionCode === 0x04) return Math.floor(dataBytes / 2)
  44. return 0
  45. }
  46. function getMaxWriteMultipleRegisterQuantity(maxFrameBytes = MAX_MODBUS_DMA_BYTES) {
  47. const frameBytes = normalizeMaxFrameBytes(maxFrameBytes)
  48. if (frameBytes === UNLIMITED_FRAME_BYTES) return 0xFFFF
  49. return Math.max(0, Math.floor((frameBytes - MODBUS_WRITE_MULTIPLE_REQUEST_OVERHEAD) / 2))
  50. }
  51. function buildReadFrame(slaveAddress, functionCode, address, quantity, options = {}) {
  52. const slave = toByte(slaveAddress, '从站地址')
  53. const command = toByte(functionCode, '功能码')
  54. const startAddress = toWord(address, '寄存器地址')
  55. const registerQuantity = toWord(quantity, '读取数量')
  56. const maxQuantity = getMaxReadQuantity(command, options.maxFrameBytes)
  57. if (![0x01, 0x02, 0x03, 0x04].includes(command)) {
  58. throw new Error('当前功能码不是读取命令')
  59. }
  60. if (registerQuantity === 0) {
  61. throw new Error('读取数量必须大于 0')
  62. }
  63. if ([0x03, 0x04].includes(command) && maxQuantity > 0 && registerQuantity > maxQuantity) {
  64. throw new Error(`单帧最多读取 ${maxQuantity} 个寄存器`)
  65. }
  66. if ((command === 0x01 || command === 0x02) && maxQuantity > 0 && registerQuantity > maxQuantity) {
  67. throw new Error(`单帧最多读取 ${maxQuantity} 个位状态`)
  68. }
  69. return appendCrc16Modbus(
  70. [slave, command].concat(splitWord(startAddress), splitWord(registerQuantity)),
  71. MODBUS_CRC_OPTIONS
  72. )
  73. }
  74. function buildWriteSingleCoilFrame(slaveAddress, address, checked) {
  75. const slave = toByte(slaveAddress, '从站地址')
  76. const startAddress = toWord(address, '线圈地址')
  77. const outputValue = checked ? 0xFF00 : 0x0000
  78. return appendCrc16Modbus(
  79. [slave, 0x05].concat(splitWord(startAddress), splitWord(outputValue)),
  80. MODBUS_CRC_OPTIONS
  81. )
  82. }
  83. function buildWriteSingleRegisterFrame(slaveAddress, address, value) {
  84. const slave = toByte(slaveAddress, '从站地址')
  85. const startAddress = toWord(address, '寄存器地址')
  86. const registerValue = toWord(value, '写入值')
  87. return appendCrc16Modbus(
  88. [slave, 0x06].concat(splitWord(startAddress), splitWord(registerValue)),
  89. MODBUS_CRC_OPTIONS
  90. )
  91. }
  92. function buildWriteMultipleRegistersFrame(slaveAddress, address, values, options = {}) {
  93. const slave = toByte(slaveAddress, '从站地址')
  94. const startAddress = toWord(address, '寄存器地址')
  95. const maxQuantity = getMaxWriteMultipleRegisterQuantity(options.maxFrameBytes)
  96. if (!Array.isArray(values) || values.length === 0) {
  97. throw new Error('请输入至少一个寄存器写入值')
  98. }
  99. if (maxQuantity > 0 && values.length > maxQuantity) {
  100. throw new Error(`单帧最多写入 ${maxQuantity} 个寄存器`)
  101. }
  102. const registerBytes = values.reduce((result, value) => {
  103. return result.concat(splitWord(toWord(value, '写入值')))
  104. }, [])
  105. return appendCrc16Modbus(
  106. [slave, 0x10]
  107. .concat(splitWord(startAddress), splitWord(values.length), [registerBytes.length], registerBytes),
  108. MODBUS_CRC_OPTIONS
  109. )
  110. }
  111. function formatHex(bytes) {
  112. return bytes.map((byte) => byte.toString(16).padStart(2, '0').toUpperCase()).join(' ')
  113. }
  114. function getReadResponseByteLength(functionCode, quantity) {
  115. if (functionCode === 0x01 || functionCode === 0x02) return MODBUS_READ_RESPONSE_OVERHEAD + Math.ceil(Number(quantity || 0) / 8)
  116. if (functionCode === 0x03 || functionCode === 0x04) return MODBUS_READ_RESPONSE_OVERHEAD + Number(quantity || 0) * 2
  117. return 0
  118. }
  119. module.exports = {
  120. MAX_MODBUS_DMA_BYTES,
  121. MAX_READ_COIL_QUANTITY,
  122. MAX_READ_REGISTER_QUANTITY,
  123. MAX_WRITE_MULTIPLE_REGISTER_QUANTITY,
  124. MODBUS_CRC_OPTIONS,
  125. UNLIMITED_FRAME_BYTES,
  126. buildReadFrame,
  127. buildWriteMultipleRegistersFrame,
  128. buildWriteSingleCoilFrame,
  129. buildWriteSingleRegisterFrame,
  130. formatHex,
  131. getMaxWriteMultipleRegisterQuantity,
  132. getMaxReadQuantity,
  133. getReadResponseByteLength,
  134. hasValidCrc16Modbus
  135. }