1
0

frame.js 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264
  1. const {
  2. BYTE_ORDER_LOW,
  3. appendCrc16Modbus,
  4. hasValidCrc16Modbus
  5. } = require('../../utils/crc.js')
  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 MODBUS_CODE_INFO_FUNCTION_CODE = 0x40
  13. const MODBUS_DEBUG_READ_FUNCTION_CODE = 0x41
  14. const MODBUS_DEBUG_WRITE_FUNCTION_CODE = 0x42
  15. const MODBUS_CODE_INFO_RESPONSE_LENGTH = 9
  16. const MODBUS_DEBUG_READ_RESPONSE_OVERHEAD = 6
  17. const MODBUS_DEBUG_WRITE_REQUEST_OVERHEAD = 9
  18. const MAX_READ_REGISTER_QUANTITY = Math.floor((MAX_MODBUS_DMA_BYTES - MODBUS_READ_RESPONSE_OVERHEAD) / 2)
  19. const MAX_READ_COIL_QUANTITY = (MAX_MODBUS_DMA_BYTES - MODBUS_READ_RESPONSE_OVERHEAD) * 8
  20. const MAX_WRITE_MULTIPLE_REGISTER_QUANTITY = Math.floor((MAX_MODBUS_DMA_BYTES - MODBUS_WRITE_MULTIPLE_REQUEST_OVERHEAD) / 2)
  21. const UNLIMITED_FRAME_BYTES = 0
  22. function toByte(value, label) {
  23. if (!Number.isInteger(value) || value < 0 || value > 0xFF) {
  24. throw new Error(`${label}必须在 0x00 至 0xFF 之间`)
  25. }
  26. return value
  27. }
  28. function toWord(value, label) {
  29. if (!Number.isInteger(value) || value < 0 || value > 0xFFFF) {
  30. throw new Error(`${label}必须在 0x0000 至 0xFFFF 之间`)
  31. }
  32. return value
  33. }
  34. function toMemoryType(value) {
  35. const memoryType = toByte(value, '内存区域')
  36. if (memoryType > 0x03) {
  37. throw new Error('内存区域必须为 data/idata/xdata/code')
  38. }
  39. return memoryType
  40. }
  41. function toDebugByteLength(value, label = '字节长度') {
  42. const byteLength = toWord(value, label)
  43. if (byteLength === 0) {
  44. throw new Error(`${label}必须大于 0`)
  45. }
  46. if (byteLength > 0xFF) {
  47. throw new Error('单帧字节长度不能超过 255')
  48. }
  49. return byteLength
  50. }
  51. function splitWord(value) {
  52. return [(value >> 8) & 0xFF, value & 0xFF]
  53. }
  54. function normalizeMaxFrameBytes(maxFrameBytes = MAX_MODBUS_DMA_BYTES) {
  55. const numberValue = Number(maxFrameBytes)
  56. if (Number.isFinite(numberValue) && Math.round(numberValue) === UNLIMITED_FRAME_BYTES) return UNLIMITED_FRAME_BYTES
  57. if (Number.isFinite(numberValue) && numberValue > 0) return Math.round(numberValue)
  58. return MAX_MODBUS_DMA_BYTES
  59. }
  60. function getMaxReadQuantity(functionCode, maxFrameBytes = MAX_MODBUS_DMA_BYTES) {
  61. const frameBytes = normalizeMaxFrameBytes(maxFrameBytes)
  62. if (frameBytes === UNLIMITED_FRAME_BYTES) return 0xFFFF
  63. const dataBytes = frameBytes - MODBUS_READ_RESPONSE_OVERHEAD
  64. if (dataBytes <= 0) return 0
  65. if (functionCode === 0x01 || functionCode === 0x02) return dataBytes * 8
  66. if (functionCode === 0x03 || functionCode === 0x04) return Math.floor(dataBytes / 2)
  67. return 0
  68. }
  69. function getMaxWriteMultipleRegisterQuantity(maxFrameBytes = MAX_MODBUS_DMA_BYTES) {
  70. const frameBytes = normalizeMaxFrameBytes(maxFrameBytes)
  71. if (frameBytes === UNLIMITED_FRAME_BYTES) return 0xFFFF
  72. return Math.max(0, Math.floor((frameBytes - MODBUS_WRITE_MULTIPLE_REQUEST_OVERHEAD) / 2))
  73. }
  74. function getMaxDebugReadByteLength(maxFrameBytes = MAX_MODBUS_DMA_BYTES) {
  75. const frameBytes = normalizeMaxFrameBytes(maxFrameBytes)
  76. if (frameBytes === UNLIMITED_FRAME_BYTES) return 0xFF
  77. return Math.max(0, Math.min(0xFF, frameBytes - MODBUS_DEBUG_READ_RESPONSE_OVERHEAD))
  78. }
  79. function getMaxDebugWriteByteLength(maxFrameBytes = MAX_MODBUS_DMA_BYTES) {
  80. const frameBytes = normalizeMaxFrameBytes(maxFrameBytes)
  81. if (frameBytes === UNLIMITED_FRAME_BYTES) return 0xFF
  82. return Math.max(0, Math.min(0xFF, frameBytes - MODBUS_DEBUG_WRITE_REQUEST_OVERHEAD))
  83. }
  84. function buildReadFrame(slaveAddress, functionCode, address, quantity, options = {}) {
  85. const slave = toByte(slaveAddress, '从站地址')
  86. const command = toByte(functionCode, '功能码')
  87. const startAddress = toWord(address, '寄存器地址')
  88. const registerQuantity = toWord(quantity, '读取数量')
  89. const maxQuantity = getMaxReadQuantity(command, options.maxFrameBytes)
  90. if (![0x01, 0x02, 0x03, 0x04].includes(command)) {
  91. throw new Error('当前功能码不是读取命令')
  92. }
  93. if (registerQuantity === 0) {
  94. throw new Error('读取数量必须大于 0')
  95. }
  96. if ([0x03, 0x04].includes(command) && maxQuantity > 0 && registerQuantity > maxQuantity) {
  97. throw new Error(`单帧最多读取 ${maxQuantity} 个寄存器`)
  98. }
  99. if ((command === 0x01 || command === 0x02) && maxQuantity > 0 && registerQuantity > maxQuantity) {
  100. throw new Error(`单帧最多读取 ${maxQuantity} 个位状态`)
  101. }
  102. return appendCrc16Modbus(
  103. [slave, command].concat(splitWord(startAddress), splitWord(registerQuantity)),
  104. MODBUS_CRC_OPTIONS
  105. )
  106. }
  107. function buildWriteSingleCoilFrame(slaveAddress, address, checked) {
  108. const slave = toByte(slaveAddress, '从站地址')
  109. const startAddress = toWord(address, '线圈地址')
  110. const outputValue = checked ? 0xFF00 : 0x0000
  111. return appendCrc16Modbus(
  112. [slave, 0x05].concat(splitWord(startAddress), splitWord(outputValue)),
  113. MODBUS_CRC_OPTIONS
  114. )
  115. }
  116. function buildWriteSingleRegisterFrame(slaveAddress, address, value) {
  117. const slave = toByte(slaveAddress, '从站地址')
  118. const startAddress = toWord(address, '寄存器地址')
  119. const registerValue = toWord(value, '写入值')
  120. return appendCrc16Modbus(
  121. [slave, 0x06].concat(splitWord(startAddress), splitWord(registerValue)),
  122. MODBUS_CRC_OPTIONS
  123. )
  124. }
  125. function buildWriteMultipleRegistersFrame(slaveAddress, address, values, options = {}) {
  126. const slave = toByte(slaveAddress, '从站地址')
  127. const startAddress = toWord(address, '寄存器地址')
  128. const maxQuantity = getMaxWriteMultipleRegisterQuantity(options.maxFrameBytes)
  129. if (!Array.isArray(values) || values.length === 0) {
  130. throw new Error('请输入至少一个寄存器写入值')
  131. }
  132. if (maxQuantity > 0 && values.length > maxQuantity) {
  133. throw new Error(`单帧最多写入 ${maxQuantity} 个寄存器`)
  134. }
  135. const registerBytes = values.reduce((result, value) => {
  136. return result.concat(splitWord(toWord(value, '写入值')))
  137. }, [])
  138. return appendCrc16Modbus(
  139. [slave, 0x10]
  140. .concat(splitWord(startAddress), splitWord(values.length), [registerBytes.length], registerBytes),
  141. MODBUS_CRC_OPTIONS
  142. )
  143. }
  144. function buildCodeInfoQueryFrame(slaveAddress) {
  145. const slave = toByte(slaveAddress, '从站地址')
  146. return appendCrc16Modbus(
  147. [slave, MODBUS_CODE_INFO_FUNCTION_CODE],
  148. MODBUS_CRC_OPTIONS
  149. )
  150. }
  151. function buildDebugReadMemoryFrame(slaveAddress, memoryType, address, byteLength, options = {}) {
  152. const slave = toByte(slaveAddress, '从站地址')
  153. const memType = toMemoryType(memoryType)
  154. const startAddress = toWord(address, '内存地址')
  155. const length = toDebugByteLength(byteLength)
  156. const maxByteLength = getMaxDebugReadByteLength(options.maxFrameBytes)
  157. if (maxByteLength > 0 && length > maxByteLength) {
  158. throw new Error(`单帧最多读取 ${maxByteLength} 字节`)
  159. }
  160. return appendCrc16Modbus(
  161. [slave, MODBUS_DEBUG_READ_FUNCTION_CODE, memType].concat(splitWord(startAddress), splitWord(length)),
  162. MODBUS_CRC_OPTIONS
  163. )
  164. }
  165. function buildDebugWriteMemoryFrame(slaveAddress, memoryType, address, bytes, options = {}) {
  166. const slave = toByte(slaveAddress, '从站地址')
  167. const memType = toMemoryType(memoryType)
  168. const startAddress = toWord(address, '内存地址')
  169. const dataBytes = Array.prototype.slice.call(bytes || []).map((byte) => toByte(Number(byte), '写入字节'))
  170. const length = toDebugByteLength(dataBytes.length)
  171. const maxByteLength = getMaxDebugWriteByteLength(options.maxFrameBytes)
  172. if (memType === 0x03) {
  173. throw new Error('code 区暂不支持写入')
  174. }
  175. if (maxByteLength > 0 && length > maxByteLength) {
  176. throw new Error(`单帧最多写入 ${maxByteLength} 字节`)
  177. }
  178. return appendCrc16Modbus(
  179. [slave, MODBUS_DEBUG_WRITE_FUNCTION_CODE, memType]
  180. .concat(splitWord(startAddress), splitWord(length), dataBytes),
  181. MODBUS_CRC_OPTIONS
  182. )
  183. }
  184. function formatHex(bytes) {
  185. return bytes.map((byte) => byte.toString(16).padStart(2, '0').toUpperCase()).join(' ')
  186. }
  187. function getReadResponseByteLength(functionCode, quantity) {
  188. if (functionCode === 0x01 || functionCode === 0x02) return MODBUS_READ_RESPONSE_OVERHEAD + Math.ceil(Number(quantity || 0) / 8)
  189. if (functionCode === 0x03 || functionCode === 0x04) return MODBUS_READ_RESPONSE_OVERHEAD + Number(quantity || 0) * 2
  190. if (functionCode === MODBUS_CODE_INFO_FUNCTION_CODE) return MODBUS_CODE_INFO_RESPONSE_LENGTH
  191. if (functionCode === MODBUS_DEBUG_READ_FUNCTION_CODE) return MODBUS_DEBUG_READ_RESPONSE_OVERHEAD + Number(quantity || 0)
  192. if (functionCode === MODBUS_DEBUG_WRITE_FUNCTION_CODE) return 9
  193. return 0
  194. }
  195. module.exports = {
  196. MAX_MODBUS_DMA_BYTES,
  197. MAX_READ_COIL_QUANTITY,
  198. MAX_READ_REGISTER_QUANTITY,
  199. MAX_WRITE_MULTIPLE_REGISTER_QUANTITY,
  200. MODBUS_CRC_OPTIONS,
  201. MODBUS_CODE_INFO_FUNCTION_CODE,
  202. MODBUS_CODE_INFO_RESPONSE_LENGTH,
  203. MODBUS_DEBUG_READ_FUNCTION_CODE,
  204. MODBUS_DEBUG_WRITE_FUNCTION_CODE,
  205. UNLIMITED_FRAME_BYTES,
  206. buildCodeInfoQueryFrame,
  207. buildDebugReadMemoryFrame,
  208. buildDebugWriteMemoryFrame,
  209. buildReadFrame,
  210. buildWriteMultipleRegistersFrame,
  211. buildWriteSingleCoilFrame,
  212. buildWriteSingleRegisterFrame,
  213. formatHex,
  214. getMaxDebugReadByteLength,
  215. getMaxDebugWriteByteLength,
  216. getMaxWriteMultipleRegisterQuantity,
  217. getMaxReadQuantity,
  218. getReadResponseByteLength,
  219. hasValidCrc16Modbus
  220. }