1
0

response.js 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298
  1. const {
  2. MAX_MODBUS_DMA_BYTES,
  3. MODBUS_CRC_OPTIONS,
  4. getReadResponseByteLength,
  5. hasValidCrc16Modbus
  6. } = require('./frame.js')
  7. const {
  8. padHex
  9. } = require('../../utils/base-utils.js')
  10. const {
  11. bytesToWords
  12. } = require('../../utils/binary-utils.js')
  13. const MODBUS_EXCEPTION_MESSAGES = {
  14. 0x01: '非法功能',
  15. 0x02: '非法数据地址',
  16. 0x03: '非法数据值',
  17. 0x04: '从站设备故障',
  18. 0x05: '确认',
  19. 0x06: '从站设备忙',
  20. 0x08: '存储奇偶性错误',
  21. 0x0A: '网关路径不可用',
  22. 0x0B: '网关目标设备响应失败'
  23. }
  24. const UNLIMITED_FRAME_BYTES = 0
  25. function normalizeMaxFrameBytes(maxFrameBytes = MAX_MODBUS_DMA_BYTES) {
  26. const numberValue = Number(maxFrameBytes)
  27. if (Number.isFinite(numberValue) && Math.round(numberValue) === UNLIMITED_FRAME_BYTES) return UNLIMITED_FRAME_BYTES
  28. if (Number.isFinite(numberValue) && numberValue > 0) return Math.round(numberValue)
  29. return MAX_MODBUS_DMA_BYTES
  30. }
  31. function parseModbusResponse(bytes) {
  32. if (!Array.isArray(bytes) || bytes.length < 5 || !hasValidCrc16Modbus(bytes, MODBUS_CRC_OPTIONS)) return null
  33. const slaveAddress = bytes[0]
  34. const functionCode = bytes[1]
  35. if (functionCode & 0x80) {
  36. return {
  37. exceptionCode: bytes[2],
  38. functionCode,
  39. isException: true,
  40. slaveAddress,
  41. sourceFunctionCode: functionCode & 0x7F
  42. }
  43. }
  44. if (functionCode === 0x01 || functionCode === 0x02) {
  45. const byteCount = bytes[2]
  46. const dataEnd = 3 + byteCount
  47. if (bytes.length < dataEnd + 2) return null
  48. return {
  49. byteCount,
  50. dataBytes: bytes.slice(3, dataEnd),
  51. functionCode,
  52. isException: false,
  53. slaveAddress
  54. }
  55. }
  56. if (functionCode === 0x03 || functionCode === 0x04) {
  57. const byteCount = bytes[2]
  58. const dataEnd = 3 + byteCount
  59. if (bytes.length < dataEnd + 2) return null
  60. return {
  61. byteCount,
  62. dataBytes: bytes.slice(3, dataEnd),
  63. functionCode,
  64. isException: false,
  65. slaveAddress,
  66. words: bytesToWords(bytes.slice(3, dataEnd))
  67. }
  68. }
  69. if (functionCode === 0x05 || functionCode === 0x06 || functionCode === 0x10) {
  70. return {
  71. address: ((bytes[2] << 8) | bytes[3]) & 0xFFFF,
  72. functionCode,
  73. isException: false,
  74. quantityOrValue: ((bytes[4] << 8) | bytes[5]) & 0xFFFF,
  75. slaveAddress
  76. }
  77. }
  78. return {
  79. functionCode,
  80. isException: false,
  81. slaveAddress
  82. }
  83. }
  84. function parseModbusRequest(bytes) {
  85. if (!Array.isArray(bytes) || bytes.length < 6 || !hasValidCrc16Modbus(bytes, MODBUS_CRC_OPTIONS)) return null
  86. const slaveAddress = bytes[0]
  87. const functionCode = bytes[1]
  88. const address = ((bytes[2] << 8) | bytes[3]) & 0xFFFF
  89. let quantity = 1
  90. let value
  91. if (functionCode === 0x01 || functionCode === 0x02 || functionCode === 0x03 || functionCode === 0x04 || functionCode === 0x10) {
  92. quantity = ((bytes[4] << 8) | bytes[5]) & 0xFFFF
  93. }
  94. if (functionCode === 0x05 || functionCode === 0x06) {
  95. value = ((bytes[4] << 8) | bytes[5]) & 0xFFFF
  96. }
  97. return {
  98. address,
  99. functionCode,
  100. kind: 'raw-hex',
  101. quantity,
  102. value,
  103. slaveAddress
  104. }
  105. }
  106. function getExpectedResponseLength(expected, responseFunctionCode, responseBytes = []) {
  107. if (!expected) return 0
  108. if (responseFunctionCode === (expected.functionCode | 0x80)) {
  109. return 5
  110. }
  111. if (responseFunctionCode === 0x01 || responseFunctionCode === 0x02) {
  112. if (responseBytes.length < 3) return 0
  113. return 3 + Number(responseBytes[2] || 0) + 2
  114. }
  115. if (responseFunctionCode === 0x03 || responseFunctionCode === 0x04) {
  116. if (responseBytes.length < 3) return 0
  117. return 3 + Number(responseBytes[2] || 0) + 2
  118. }
  119. if (responseFunctionCode === 0x05 || responseFunctionCode === 0x06 || responseFunctionCode === 0x10) {
  120. return 8
  121. }
  122. return 0
  123. }
  124. function isExpectedResponse(response, expected) {
  125. if (response.functionCode === 0x01 || response.functionCode === 0x02) {
  126. return Array.isArray(response.dataBytes) && response.dataBytes.length >= Math.ceil(expected.quantity / 8)
  127. }
  128. if (response.functionCode === 0x03 || response.functionCode === 0x04) {
  129. return Array.isArray(response.words) && response.words.length >= expected.quantity
  130. }
  131. if (response.functionCode === 0x10) {
  132. return response.address === expected.address && response.quantityOrValue === expected.quantity
  133. }
  134. if (response.functionCode === 0x05 || response.functionCode === 0x06) {
  135. if (response.address !== expected.address) return false
  136. if (Number.isInteger(expected.value)) return response.quantityOrValue === expected.value
  137. return true
  138. }
  139. return true
  140. }
  141. function getExceptionText(code) {
  142. return MODBUS_EXCEPTION_MESSAGES[code] || '未知异常'
  143. }
  144. function formatExceptionMessage(response) {
  145. const sourceFunctionCode = response && response.sourceFunctionCode
  146. const exceptionCode = response && response.exceptionCode
  147. const exceptionText = getExceptionText(exceptionCode)
  148. return `设备返回异常帧:功能码 0x${padHex(sourceFunctionCode, 2)},异常码 0x${padHex(exceptionCode, 2)}(${exceptionText})`
  149. }
  150. function getReadBufferHint(expected) {
  151. return expected ? getReadResponseByteLength(expected.functionCode, expected.quantity) : 0
  152. }
  153. function alignResponseBuffer(buffer, expected) {
  154. if (!Array.isArray(buffer) || !buffer.length || !expected) return
  155. const expectedFunctionCodes = [expected.functionCode, expected.functionCode | 0x80]
  156. let matchIndex = -1
  157. for (let index = 0; index < buffer.length - 1; index += 1) {
  158. if (buffer[index] !== expected.slaveAddress) continue
  159. if (!expectedFunctionCodes.includes(buffer[index + 1])) continue
  160. matchIndex = index
  161. break
  162. }
  163. if (matchIndex > 0) {
  164. buffer.splice(0, matchIndex)
  165. } else if (matchIndex < 0 && buffer.length > 2) {
  166. buffer.splice(0, buffer.length - 1)
  167. }
  168. }
  169. function readResponseFromBuffer(buffer, expected, options = {}) {
  170. if (!Array.isArray(buffer) || !buffer.length || !expected) {
  171. return {
  172. status: 'pending'
  173. }
  174. }
  175. alignResponseBuffer(buffer, expected)
  176. while (buffer.length >= 2) {
  177. const responseFunctionCode = buffer[1]
  178. const responseLength = getExpectedResponseLength(expected, responseFunctionCode, buffer)
  179. if (!responseLength) {
  180. return {
  181. status: 'pending'
  182. }
  183. }
  184. const frameLimit = normalizeMaxFrameBytes(
  185. options.maxFrameBytes === undefined ? expected.maxFrameBytes : options.maxFrameBytes
  186. )
  187. if (frameLimit > 0 && responseLength > frameLimit) {
  188. return {
  189. frameLimit,
  190. responseLength,
  191. status: 'frame-too-long'
  192. }
  193. }
  194. if (buffer.length < responseLength) {
  195. return {
  196. status: 'pending'
  197. }
  198. }
  199. const frameBytes = buffer.slice(0, responseLength)
  200. const response = parseModbusResponse(frameBytes)
  201. if (!response) {
  202. return {
  203. frameBytes,
  204. status: 'invalid'
  205. }
  206. }
  207. const responseCode = response.isException ? response.sourceFunctionCode : response.functionCode
  208. if (response.slaveAddress !== expected.slaveAddress || responseCode !== expected.functionCode) {
  209. buffer.shift()
  210. alignResponseBuffer(buffer, expected)
  211. continue
  212. }
  213. if (response.isException) {
  214. return {
  215. message: formatExceptionMessage(response),
  216. response,
  217. status: 'exception'
  218. }
  219. }
  220. if (!isExpectedResponse(response, expected)) {
  221. return {
  222. response,
  223. status: 'mismatch'
  224. }
  225. }
  226. buffer.splice(0, responseLength)
  227. return {
  228. response,
  229. status: 'complete'
  230. }
  231. }
  232. return {
  233. status: 'pending'
  234. }
  235. }
  236. module.exports = {
  237. MODBUS_EXCEPTION_MESSAGES,
  238. formatExceptionMessage,
  239. getExceptionText,
  240. getExpectedResponseLength,
  241. getReadBufferHint,
  242. isExpectedResponse,
  243. parseModbusRequest,
  244. parseModbusResponse,
  245. readResponseFromBuffer
  246. }