1
0

response.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413
  1. const {
  2. MAX_MODBUS_DMA_BYTES,
  3. MODBUS_CODE_INFO_FUNCTION_CODE,
  4. MODBUS_CODE_INFO_RESPONSE_LENGTH,
  5. MODBUS_CRC_OPTIONS,
  6. MODBUS_DEBUG_READ_FUNCTION_CODE,
  7. MODBUS_DEBUG_WRITE_FUNCTION_CODE,
  8. getReadResponseByteLength,
  9. hasValidCrc16Modbus
  10. } = require('./frame.js')
  11. const {
  12. padHex
  13. } = require('../../utils/base-utils.js')
  14. const {
  15. bytesToWords
  16. } = require('../../utils/binary-utils.js')
  17. const MODBUS_EXCEPTION_MESSAGES = {
  18. 0x01: '非法功能',
  19. 0x02: '非法数据地址',
  20. 0x03: '非法数据值',
  21. 0x04: '从站设备故障',
  22. 0x05: '确认',
  23. 0x06: '从站设备忙',
  24. 0x08: '存储奇偶性错误',
  25. 0x0A: '网关路径不可用',
  26. 0x0B: '网关目标设备响应失败'
  27. }
  28. const UNLIMITED_FRAME_BYTES = 0
  29. function normalizeMaxFrameBytes(maxFrameBytes = MAX_MODBUS_DMA_BYTES) {
  30. const numberValue = Number(maxFrameBytes)
  31. if (Number.isFinite(numberValue) && Math.round(numberValue) === UNLIMITED_FRAME_BYTES) return UNLIMITED_FRAME_BYTES
  32. if (Number.isFinite(numberValue) && numberValue > 0) return Math.round(numberValue)
  33. return MAX_MODBUS_DMA_BYTES
  34. }
  35. function parseModbusResponse(bytes) {
  36. if (!Array.isArray(bytes) || bytes.length < 5 || !hasValidCrc16Modbus(bytes, MODBUS_CRC_OPTIONS)) return null
  37. const slaveAddress = bytes[0]
  38. const functionCode = bytes[1]
  39. if (functionCode & 0x80) {
  40. return {
  41. exceptionCode: bytes[2],
  42. functionCode,
  43. isException: true,
  44. slaveAddress,
  45. sourceFunctionCode: functionCode & 0x7F
  46. }
  47. }
  48. if (functionCode === 0x01 || functionCode === 0x02) {
  49. const byteCount = bytes[2]
  50. const dataEnd = 3 + byteCount
  51. if (bytes.length < dataEnd + 2) return null
  52. return {
  53. byteCount,
  54. dataBytes: bytes.slice(3, dataEnd),
  55. functionCode,
  56. isException: false,
  57. slaveAddress
  58. }
  59. }
  60. if (functionCode === 0x03 || functionCode === 0x04) {
  61. const byteCount = bytes[2]
  62. const dataEnd = 3 + byteCount
  63. if (bytes.length < dataEnd + 2) return null
  64. return {
  65. byteCount,
  66. dataBytes: bytes.slice(3, dataEnd),
  67. functionCode,
  68. isException: false,
  69. slaveAddress,
  70. words: bytesToWords(bytes.slice(3, dataEnd))
  71. }
  72. }
  73. if (functionCode === MODBUS_CODE_INFO_FUNCTION_CODE) {
  74. if (bytes.length < MODBUS_CODE_INFO_RESPONSE_LENGTH) return null
  75. return {
  76. address: ((bytes[3] << 8) | bytes[4]) & 0xFFFF,
  77. byteLength: ((bytes[5] << 8) | bytes[6]) & 0xFFFF,
  78. codeInfoAddress: ((bytes[3] << 8) | bytes[4]) & 0xFFFF,
  79. codeInfoLength: ((bytes[5] << 8) | bytes[6]) & 0xFFFF,
  80. functionCode,
  81. isException: false,
  82. memoryType: bytes[2],
  83. slaveAddress
  84. }
  85. }
  86. if (functionCode === MODBUS_DEBUG_READ_FUNCTION_CODE) {
  87. const memoryType = bytes[2]
  88. const byteCount = bytes[3]
  89. const dataEnd = 4 + byteCount
  90. if (bytes.length < dataEnd + 2) return null
  91. return {
  92. byteCount,
  93. dataBytes: bytes.slice(4, dataEnd),
  94. functionCode,
  95. isException: false,
  96. memoryType,
  97. slaveAddress,
  98. words: bytesToWords(bytes.slice(4, dataEnd).concat(byteCount % 2 ? [0] : []))
  99. }
  100. }
  101. if (functionCode === MODBUS_DEBUG_WRITE_FUNCTION_CODE) {
  102. if (bytes.length < 9) return null
  103. return {
  104. address: ((bytes[3] << 8) | bytes[4]) & 0xFFFF,
  105. byteLength: ((bytes[5] << 8) | bytes[6]) & 0xFFFF,
  106. functionCode,
  107. isException: false,
  108. memoryType: bytes[2],
  109. quantity: ((bytes[5] << 8) | bytes[6]) & 0xFFFF,
  110. slaveAddress
  111. }
  112. }
  113. if (functionCode === 0x05 || functionCode === 0x06 || functionCode === 0x10) {
  114. return {
  115. address: ((bytes[2] << 8) | bytes[3]) & 0xFFFF,
  116. functionCode,
  117. isException: false,
  118. quantityOrValue: ((bytes[4] << 8) | bytes[5]) & 0xFFFF,
  119. slaveAddress
  120. }
  121. }
  122. return {
  123. functionCode,
  124. isException: false,
  125. slaveAddress
  126. }
  127. }
  128. function parseModbusRequest(bytes) {
  129. if (!Array.isArray(bytes) || bytes.length < 4 || !hasValidCrc16Modbus(bytes, MODBUS_CRC_OPTIONS)) return null
  130. const slaveAddress = bytes[0]
  131. const functionCode = bytes[1]
  132. if (functionCode === MODBUS_CODE_INFO_FUNCTION_CODE) {
  133. return {
  134. functionCode,
  135. kind: 'raw-hex',
  136. quantity: 1,
  137. slaveAddress
  138. }
  139. }
  140. if (bytes.length < 6) return null
  141. if (functionCode === MODBUS_DEBUG_READ_FUNCTION_CODE || functionCode === MODBUS_DEBUG_WRITE_FUNCTION_CODE) {
  142. if (bytes.length < 9) return null
  143. const memoryType = bytes[2]
  144. const address = ((bytes[3] << 8) | bytes[4]) & 0xFFFF
  145. const quantity = ((bytes[5] << 8) | bytes[6]) & 0xFFFF
  146. const request = {
  147. address,
  148. functionCode,
  149. kind: 'raw-hex',
  150. memoryType,
  151. quantity,
  152. slaveAddress
  153. }
  154. if (functionCode === MODBUS_DEBUG_WRITE_FUNCTION_CODE) {
  155. if (bytes.length < 7 + quantity + 2) return null
  156. request.dataBytes = bytes.slice(7, 7 + quantity)
  157. }
  158. return request
  159. }
  160. const address = ((bytes[2] << 8) | bytes[3]) & 0xFFFF
  161. let quantity = 1
  162. let value
  163. if (functionCode === 0x01 || functionCode === 0x02 || functionCode === 0x03 || functionCode === 0x04 || functionCode === 0x10) {
  164. quantity = ((bytes[4] << 8) | bytes[5]) & 0xFFFF
  165. }
  166. if (functionCode === 0x05 || functionCode === 0x06) {
  167. value = ((bytes[4] << 8) | bytes[5]) & 0xFFFF
  168. }
  169. return {
  170. address,
  171. functionCode,
  172. kind: 'raw-hex',
  173. quantity,
  174. value,
  175. slaveAddress
  176. }
  177. }
  178. function getExpectedResponseLength(expected, responseFunctionCode, responseBytes = []) {
  179. if (!expected) return 0
  180. if (responseFunctionCode === (expected.functionCode | 0x80)) {
  181. return 5
  182. }
  183. if (responseFunctionCode === 0x01 || responseFunctionCode === 0x02) {
  184. if (responseBytes.length < 3) return 0
  185. return 3 + Number(responseBytes[2] || 0) + 2
  186. }
  187. if (responseFunctionCode === 0x03 || responseFunctionCode === 0x04) {
  188. if (responseBytes.length < 3) return 0
  189. return 3 + Number(responseBytes[2] || 0) + 2
  190. }
  191. if (responseFunctionCode === MODBUS_CODE_INFO_FUNCTION_CODE) {
  192. return MODBUS_CODE_INFO_RESPONSE_LENGTH
  193. }
  194. if (responseFunctionCode === MODBUS_DEBUG_READ_FUNCTION_CODE) {
  195. if (responseBytes.length < 4) return 0
  196. return 4 + Number(responseBytes[3] || 0) + 2
  197. }
  198. if (responseFunctionCode === MODBUS_DEBUG_WRITE_FUNCTION_CODE) {
  199. return 9
  200. }
  201. if (responseFunctionCode === 0x05 || responseFunctionCode === 0x06 || responseFunctionCode === 0x10) {
  202. return 8
  203. }
  204. return 0
  205. }
  206. function isExpectedResponse(response, expected) {
  207. if (response.functionCode === 0x01 || response.functionCode === 0x02) {
  208. return Array.isArray(response.dataBytes) && response.dataBytes.length >= Math.ceil(expected.quantity / 8)
  209. }
  210. if (response.functionCode === 0x03 || response.functionCode === 0x04) {
  211. return Array.isArray(response.words) && response.words.length >= expected.quantity
  212. }
  213. if (response.functionCode === MODBUS_CODE_INFO_FUNCTION_CODE) {
  214. return response.memoryType === 0x03
  215. && response.codeInfoLength > 0
  216. }
  217. if (response.functionCode === MODBUS_DEBUG_READ_FUNCTION_CODE) {
  218. return response.memoryType === expected.memoryType
  219. && Array.isArray(response.dataBytes)
  220. && response.dataBytes.length === expected.quantity
  221. }
  222. if (response.functionCode === MODBUS_DEBUG_WRITE_FUNCTION_CODE) {
  223. return response.memoryType === expected.memoryType
  224. && response.address === expected.address
  225. && response.byteLength === expected.quantity
  226. }
  227. if (response.functionCode === 0x10) {
  228. return response.address === expected.address && response.quantityOrValue === expected.quantity
  229. }
  230. if (response.functionCode === 0x05 || response.functionCode === 0x06) {
  231. if (response.address !== expected.address) return false
  232. if (Number.isInteger(expected.value)) return response.quantityOrValue === expected.value
  233. return true
  234. }
  235. return true
  236. }
  237. function getExceptionText(code) {
  238. return MODBUS_EXCEPTION_MESSAGES[code] || '未知异常'
  239. }
  240. function formatExceptionMessage(response) {
  241. const sourceFunctionCode = response && response.sourceFunctionCode
  242. const exceptionCode = response && response.exceptionCode
  243. const exceptionText = getExceptionText(exceptionCode)
  244. return `设备返回异常帧:功能码 0x${padHex(sourceFunctionCode, 2)},异常码 0x${padHex(exceptionCode, 2)}(${exceptionText})`
  245. }
  246. function getReadBufferHint(expected) {
  247. return expected ? getReadResponseByteLength(expected.functionCode, expected.quantity) : 0
  248. }
  249. function alignResponseBuffer(buffer, expected) {
  250. if (!Array.isArray(buffer) || !buffer.length || !expected) return
  251. const expectedFunctionCodes = [expected.functionCode, expected.functionCode | 0x80]
  252. let matchIndex = -1
  253. for (let index = 0; index < buffer.length - 1; index += 1) {
  254. if (buffer[index] !== expected.slaveAddress) continue
  255. if (!expectedFunctionCodes.includes(buffer[index + 1])) continue
  256. matchIndex = index
  257. break
  258. }
  259. if (matchIndex > 0) {
  260. buffer.splice(0, matchIndex)
  261. } else if (matchIndex < 0 && buffer.length > 2) {
  262. buffer.splice(0, buffer.length - 1)
  263. }
  264. }
  265. function readResponseFromBuffer(buffer, expected, options = {}) {
  266. if (!Array.isArray(buffer) || !buffer.length || !expected) {
  267. return {
  268. status: 'pending'
  269. }
  270. }
  271. alignResponseBuffer(buffer, expected)
  272. while (buffer.length >= 2) {
  273. const responseFunctionCode = buffer[1]
  274. const responseLength = getExpectedResponseLength(expected, responseFunctionCode, buffer)
  275. if (!responseLength) {
  276. return {
  277. status: 'pending'
  278. }
  279. }
  280. const frameLimit = normalizeMaxFrameBytes(
  281. options.maxFrameBytes === undefined ? expected.maxFrameBytes : options.maxFrameBytes
  282. )
  283. if (frameLimit > 0 && responseLength > frameLimit) {
  284. return {
  285. frameLimit,
  286. responseLength,
  287. status: 'frame-too-long'
  288. }
  289. }
  290. if (buffer.length < responseLength) {
  291. return {
  292. status: 'pending'
  293. }
  294. }
  295. const frameBytes = buffer.slice(0, responseLength)
  296. const response = parseModbusResponse(frameBytes)
  297. if (!response) {
  298. return {
  299. frameBytes,
  300. status: 'invalid'
  301. }
  302. }
  303. const responseCode = response.isException ? response.sourceFunctionCode : response.functionCode
  304. if (response.slaveAddress !== expected.slaveAddress || responseCode !== expected.functionCode) {
  305. buffer.shift()
  306. alignResponseBuffer(buffer, expected)
  307. continue
  308. }
  309. if (response.isException) {
  310. return {
  311. message: formatExceptionMessage(response),
  312. response,
  313. status: 'exception'
  314. }
  315. }
  316. if (!isExpectedResponse(response, expected)) {
  317. return {
  318. response,
  319. status: 'mismatch'
  320. }
  321. }
  322. buffer.splice(0, responseLength)
  323. return {
  324. response,
  325. status: 'complete'
  326. }
  327. }
  328. return {
  329. status: 'pending'
  330. }
  331. }
  332. module.exports = {
  333. MODBUS_EXCEPTION_MESSAGES,
  334. formatExceptionMessage,
  335. getExceptionText,
  336. getExpectedResponseLength,
  337. getReadBufferHint,
  338. isExpectedResponse,
  339. parseModbusRequest,
  340. parseModbusResponse,
  341. readResponseFromBuffer
  342. }