1
0

index.js 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704
  1. const {
  2. padHex
  3. } = require('../../utils/base-utils.js')
  4. const {
  5. bytesToWords
  6. } = require('../../utils/binary-utils.js')
  7. const {
  8. BYTE_ORDER_LOW,
  9. appendCrc16Modbus,
  10. hasValidCrc16Modbus
  11. } = require('../../utils/crc.js')
  12. const settingsService = require('../../store/settings-store.js')
  13. const transport = require('../../transport/ble-core.js')
  14. const {
  15. addCoilReadValues,
  16. addWordReadValues
  17. } = require('../../utils/register-value-utils.js')
  18. const PROTOCOL_NAME = 'modbus-rtu'
  19. const MODBUS_CRC_OPTIONS = {
  20. byteOrder: BYTE_ORDER_LOW
  21. }
  22. const MAX_MODBUS_DMA_BYTES = 64
  23. const MODBUS_READ_RESPONSE_OVERHEAD = 5
  24. const MODBUS_WRITE_MULTIPLE_REQUEST_OVERHEAD = 9
  25. const MAX_READ_REGISTER_QUANTITY = Math.floor((MAX_MODBUS_DMA_BYTES - MODBUS_READ_RESPONSE_OVERHEAD) / 2)
  26. const MAX_READ_COIL_QUANTITY = (MAX_MODBUS_DMA_BYTES - MODBUS_READ_RESPONSE_OVERHEAD) * 8
  27. const MAX_WRITE_MULTIPLE_REGISTER_QUANTITY = Math.floor((MAX_MODBUS_DMA_BYTES - MODBUS_WRITE_MULTIPLE_REQUEST_OVERHEAD) / 2)
  28. const UNLIMITED_FRAME_BYTES = 0
  29. const MODBUS_EXCEPTION_MESSAGES = {
  30. 0x01: '非法功能',
  31. 0x02: '非法数据地址',
  32. 0x03: '非法数据值',
  33. 0x04: '从站设备故障',
  34. 0x05: '确认',
  35. 0x06: '从站设备忙',
  36. 0x08: '存储奇偶性错误',
  37. 0x0A: '网关路径不可用',
  38. 0x0B: '网关目标设备响应失败'
  39. }
  40. function toByte(value, label) {
  41. if (!Number.isInteger(value) || value < 0 || value > 0xFF) {
  42. throw new Error(`${label}必须在 0x00 至 0xFF 之间`)
  43. }
  44. return value
  45. }
  46. function toWord(value, label) {
  47. if (!Number.isInteger(value) || value < 0 || value > 0xFFFF) {
  48. throw new Error(`${label}必须在 0x0000 至 0xFFFF 之间`)
  49. }
  50. return value
  51. }
  52. function splitWord(value) {
  53. return [(value >> 8) & 0xFF, value & 0xFF]
  54. }
  55. function normalizeMaxFrameBytes(maxFrameBytes = MAX_MODBUS_DMA_BYTES) {
  56. const numberValue = Number(maxFrameBytes)
  57. if (Number.isFinite(numberValue) && Math.round(numberValue) === UNLIMITED_FRAME_BYTES) return UNLIMITED_FRAME_BYTES
  58. if (Number.isFinite(numberValue) && numberValue > 0) return Math.round(numberValue)
  59. return MAX_MODBUS_DMA_BYTES
  60. }
  61. function getMaxReadQuantity(functionCode, maxFrameBytes = MAX_MODBUS_DMA_BYTES) {
  62. const frameBytes = normalizeMaxFrameBytes(maxFrameBytes)
  63. if (frameBytes === UNLIMITED_FRAME_BYTES) return 0xFFFF
  64. const dataBytes = frameBytes - MODBUS_READ_RESPONSE_OVERHEAD
  65. if (dataBytes <= 0) return 0
  66. if (functionCode === 0x01 || functionCode === 0x02) return dataBytes * 8
  67. if (functionCode === 0x03 || functionCode === 0x04) return Math.floor(dataBytes / 2)
  68. return 0
  69. }
  70. function getMaxWriteMultipleRegisterQuantity(maxFrameBytes = MAX_MODBUS_DMA_BYTES) {
  71. const frameBytes = normalizeMaxFrameBytes(maxFrameBytes)
  72. if (frameBytes === UNLIMITED_FRAME_BYTES) return 0xFFFF
  73. return Math.max(0, Math.floor((frameBytes - MODBUS_WRITE_MULTIPLE_REQUEST_OVERHEAD) / 2))
  74. }
  75. function buildReadFrame(slaveAddress, functionCode, address, quantity, options = {}) {
  76. const slave = toByte(slaveAddress, '从站地址')
  77. const command = toByte(functionCode, '功能码')
  78. const startAddress = toWord(address, '寄存器地址')
  79. const registerQuantity = toWord(quantity, '读取数量')
  80. const maxQuantity = getMaxReadQuantity(command, options.maxFrameBytes)
  81. if ([0x01, 0x02, 0x03, 0x04].indexOf(command) < 0) {
  82. throw new Error('当前功能码不是读取命令')
  83. }
  84. if (registerQuantity === 0) {
  85. throw new Error('读取数量必须大于 0')
  86. }
  87. if ([0x03, 0x04].indexOf(command) >= 0 && maxQuantity > 0 && registerQuantity > maxQuantity) {
  88. throw new Error(`单帧最多读取 ${maxQuantity} 个寄存器`)
  89. }
  90. if ((command === 0x01 || command === 0x02) && maxQuantity > 0 && registerQuantity > maxQuantity) {
  91. throw new Error(`单帧最多读取 ${maxQuantity} 个位状态`)
  92. }
  93. return appendCrc16Modbus(
  94. [slave, command].concat(splitWord(startAddress), splitWord(registerQuantity)),
  95. MODBUS_CRC_OPTIONS
  96. )
  97. }
  98. function buildWriteSingleCoilFrame(slaveAddress, address, checked) {
  99. const slave = toByte(slaveAddress, '从站地址')
  100. const startAddress = toWord(address, '线圈地址')
  101. const outputValue = checked ? 0xFF00 : 0x0000
  102. return appendCrc16Modbus(
  103. [slave, 0x05].concat(splitWord(startAddress), splitWord(outputValue)),
  104. MODBUS_CRC_OPTIONS
  105. )
  106. }
  107. function buildWriteSingleRegisterFrame(slaveAddress, address, value) {
  108. const slave = toByte(slaveAddress, '从站地址')
  109. const startAddress = toWord(address, '寄存器地址')
  110. const registerValue = toWord(value, '写入值')
  111. return appendCrc16Modbus(
  112. [slave, 0x06].concat(splitWord(startAddress), splitWord(registerValue)),
  113. MODBUS_CRC_OPTIONS
  114. )
  115. }
  116. function buildWriteMultipleRegistersFrame(slaveAddress, address, values, options = {}) {
  117. const slave = toByte(slaveAddress, '从站地址')
  118. const startAddress = toWord(address, '寄存器地址')
  119. const maxQuantity = getMaxWriteMultipleRegisterQuantity(options.maxFrameBytes)
  120. if (!Array.isArray(values) || values.length === 0) {
  121. throw new Error('请输入至少一个寄存器写入值')
  122. }
  123. if (maxQuantity > 0 && values.length > maxQuantity) {
  124. throw new Error(`单帧最多写入 ${maxQuantity} 个寄存器`)
  125. }
  126. const registerBytes = values.reduce((result, value) => {
  127. return result.concat(splitWord(toWord(value, '写入值')))
  128. }, [])
  129. return appendCrc16Modbus(
  130. [slave, 0x10]
  131. .concat(splitWord(startAddress), splitWord(values.length), [registerBytes.length], registerBytes),
  132. MODBUS_CRC_OPTIONS
  133. )
  134. }
  135. function formatHex(bytes) {
  136. return bytes.map((byte) => byte.toString(16).padStart(2, '0').toUpperCase()).join(' ')
  137. }
  138. function getReadResponseByteLength(functionCode, quantity) {
  139. if (functionCode === 0x01 || functionCode === 0x02) return MODBUS_READ_RESPONSE_OVERHEAD + Math.ceil(Number(quantity || 0) / 8)
  140. if (functionCode === 0x03 || functionCode === 0x04) return MODBUS_READ_RESPONSE_OVERHEAD + Number(quantity || 0) * 2
  141. return 0
  142. }
  143. function parseModbusResponse(bytes) {
  144. if (!Array.isArray(bytes) || bytes.length < 5 || !hasValidCrc16Modbus(bytes, MODBUS_CRC_OPTIONS)) return null
  145. const slaveAddress = bytes[0]
  146. const functionCode = bytes[1]
  147. if (functionCode & 0x80) {
  148. return {
  149. exceptionCode: bytes[2],
  150. functionCode,
  151. isException: true,
  152. protocol: PROTOCOL_NAME,
  153. slaveAddress,
  154. sourceFunctionCode: functionCode & 0x7F
  155. }
  156. }
  157. if (functionCode === 0x01 || functionCode === 0x02) {
  158. const byteCount = bytes[2]
  159. const dataEnd = 3 + byteCount
  160. if (bytes.length < dataEnd + 2) return null
  161. return {
  162. byteCount,
  163. dataBytes: bytes.slice(3, dataEnd),
  164. functionCode,
  165. isException: false,
  166. protocol: PROTOCOL_NAME,
  167. slaveAddress
  168. }
  169. }
  170. if (functionCode === 0x03 || functionCode === 0x04) {
  171. const byteCount = bytes[2]
  172. const dataEnd = 3 + byteCount
  173. if (bytes.length < dataEnd + 2) return null
  174. return {
  175. byteCount,
  176. dataBytes: bytes.slice(3, dataEnd),
  177. functionCode,
  178. isException: false,
  179. protocol: PROTOCOL_NAME,
  180. slaveAddress,
  181. words: bytesToWords(bytes.slice(3, dataEnd))
  182. }
  183. }
  184. if (functionCode === 0x05 || functionCode === 0x06 || functionCode === 0x10) {
  185. return {
  186. address: ((bytes[2] << 8) | bytes[3]) & 0xFFFF,
  187. functionCode,
  188. isException: false,
  189. protocol: PROTOCOL_NAME,
  190. quantityOrValue: ((bytes[4] << 8) | bytes[5]) & 0xFFFF,
  191. slaveAddress
  192. }
  193. }
  194. return {
  195. functionCode,
  196. isException: false,
  197. protocol: PROTOCOL_NAME,
  198. slaveAddress
  199. }
  200. }
  201. function parseModbusRequest(bytes) {
  202. if (!Array.isArray(bytes) || bytes.length < 4 || !hasValidCrc16Modbus(bytes, MODBUS_CRC_OPTIONS)) return null
  203. const slaveAddress = bytes[0]
  204. const functionCode = bytes[1]
  205. if (bytes.length < 6) return null
  206. const address = ((bytes[2] << 8) | bytes[3]) & 0xFFFF
  207. let quantity = 1
  208. let value
  209. if (functionCode === 0x01 || functionCode === 0x02 || functionCode === 0x03 || functionCode === 0x04 || functionCode === 0x10) {
  210. quantity = ((bytes[4] << 8) | bytes[5]) & 0xFFFF
  211. }
  212. if (functionCode === 0x05 || functionCode === 0x06) {
  213. value = ((bytes[4] << 8) | bytes[5]) & 0xFFFF
  214. }
  215. return {
  216. address,
  217. functionCode,
  218. kind: 'raw-hex',
  219. protocol: PROTOCOL_NAME,
  220. quantity,
  221. value,
  222. slaveAddress
  223. }
  224. }
  225. function getExpectedResponseLength(expected, responseFunctionCode, responseBytes = []) {
  226. if (!expected) return 0
  227. if (responseFunctionCode === (expected.functionCode | 0x80)) {
  228. return 5
  229. }
  230. if (responseFunctionCode === 0x01 || responseFunctionCode === 0x02) {
  231. if (responseBytes.length < 3) return 0
  232. return 3 + Number(responseBytes[2] || 0) + 2
  233. }
  234. if (responseFunctionCode === 0x03 || responseFunctionCode === 0x04) {
  235. if (responseBytes.length < 3) return 0
  236. return 3 + Number(responseBytes[2] || 0) + 2
  237. }
  238. if (responseFunctionCode === 0x05 || responseFunctionCode === 0x06 || responseFunctionCode === 0x10) {
  239. return 8
  240. }
  241. return 0
  242. }
  243. function isExpectedResponse(response, expected) {
  244. if (response.functionCode === 0x01 || response.functionCode === 0x02) {
  245. return Array.isArray(response.dataBytes) && response.dataBytes.length >= Math.ceil(expected.quantity / 8)
  246. }
  247. if (response.functionCode === 0x03 || response.functionCode === 0x04) {
  248. return Array.isArray(response.words) && response.words.length >= expected.quantity
  249. }
  250. if (response.functionCode === 0x10) {
  251. return response.address === expected.address && response.quantityOrValue === expected.quantity
  252. }
  253. if (response.functionCode === 0x05 || response.functionCode === 0x06) {
  254. if (response.address !== expected.address) return false
  255. if (Number.isInteger(expected.value)) return response.quantityOrValue === expected.value
  256. return true
  257. }
  258. return true
  259. }
  260. function getExceptionText(code) {
  261. return MODBUS_EXCEPTION_MESSAGES[code] || '未知异常'
  262. }
  263. function formatExceptionMessage(response) {
  264. const sourceFunctionCode = response && response.sourceFunctionCode
  265. const exceptionCode = response && response.exceptionCode
  266. const exceptionText = getExceptionText(exceptionCode)
  267. return `设备返回异常帧:功能码 0x${padHex(sourceFunctionCode, 2)},异常码 0x${padHex(exceptionCode, 2)}(${exceptionText})`
  268. }
  269. function getReadBufferHint(expected) {
  270. return expected ? getReadResponseByteLength(expected.functionCode, expected.quantity) : 0
  271. }
  272. function alignResponseBuffer(buffer, expected) {
  273. if (!Array.isArray(buffer) || !buffer.length || !expected) return
  274. const expectedFunctionCodes = [expected.functionCode, expected.functionCode | 0x80]
  275. let matchIndex = -1
  276. for (let index = 0; index < buffer.length - 1; index += 1) {
  277. if (buffer[index] !== expected.slaveAddress) continue
  278. if (expectedFunctionCodes.indexOf(buffer[index + 1]) < 0) continue
  279. matchIndex = index
  280. break
  281. }
  282. if (matchIndex > 0) {
  283. buffer.splice(0, matchIndex)
  284. } else if (matchIndex < 0 && buffer.length > 2) {
  285. buffer.splice(0, buffer.length - 1)
  286. }
  287. }
  288. function readResponseFromBuffer(buffer, expected, options = {}) {
  289. if (!Array.isArray(buffer) || !buffer.length || !expected) {
  290. return {
  291. status: 'pending'
  292. }
  293. }
  294. alignResponseBuffer(buffer, expected)
  295. while (buffer.length >= 2) {
  296. const responseFunctionCode = buffer[1]
  297. const responseLength = getExpectedResponseLength(expected, responseFunctionCode, buffer)
  298. if (!responseLength) {
  299. return {
  300. status: 'pending'
  301. }
  302. }
  303. const frameLimit = normalizeMaxFrameBytes(
  304. options.maxFrameBytes === undefined ? expected.maxFrameBytes : options.maxFrameBytes
  305. )
  306. if (frameLimit > 0 && responseLength > frameLimit) {
  307. return {
  308. frameLimit,
  309. responseLength,
  310. status: 'frame-too-long'
  311. }
  312. }
  313. if (buffer.length < responseLength) {
  314. return {
  315. status: 'pending'
  316. }
  317. }
  318. const frameBytes = buffer.slice(0, responseLength)
  319. const response = parseModbusResponse(frameBytes)
  320. if (!response) {
  321. return {
  322. frameBytes,
  323. status: 'invalid'
  324. }
  325. }
  326. const responseCode = response.isException ? response.sourceFunctionCode : response.functionCode
  327. if (response.slaveAddress !== expected.slaveAddress || responseCode !== expected.functionCode) {
  328. buffer.shift()
  329. alignResponseBuffer(buffer, expected)
  330. continue
  331. }
  332. if (response.isException) {
  333. return {
  334. message: formatExceptionMessage(response),
  335. response,
  336. status: 'exception'
  337. }
  338. }
  339. if (!isExpectedResponse(response, expected)) {
  340. return {
  341. response,
  342. status: 'mismatch'
  343. }
  344. }
  345. buffer.splice(0, responseLength)
  346. return {
  347. response,
  348. status: 'complete'
  349. }
  350. }
  351. return {
  352. status: 'pending'
  353. }
  354. }
  355. function getSharedSlaveAddress(title = '从机地址错误') {
  356. try {
  357. return settingsService.getModbusSlaveAddress()
  358. } catch (error) {
  359. transport.showCommandAlert(title, error.message)
  360. return null
  361. }
  362. }
  363. function formatAddress(value) {
  364. return Number(value || 0).toString(16).toUpperCase()
  365. }
  366. function getChunkLabel(label, chunks, chunk) {
  367. if (!label || chunks.length <= 1) return label
  368. return `${label} ${formatAddress(chunk.address)}-${formatAddress(chunk.address + chunk.quantity - 1)}`
  369. }
  370. function isBroadcastAddress(slaveAddress) {
  371. return Number(slaveAddress) === 0
  372. }
  373. function showBroadcastReadAlert(label) {
  374. transport.showCommandAlert(label || '读取命令错误', '广播地址 0x00 不支持读取')
  375. }
  376. function splitQuantity(startAddress, quantity, maxQuantity) {
  377. const chunks = []
  378. let address = Number(startAddress) || 0
  379. let remaining = Math.max(0, Math.floor(Number(quantity) || 0))
  380. const chunkLimit = Math.max(1, Math.floor(Number(maxQuantity) || remaining || 1))
  381. while (remaining > 0) {
  382. const chunkQuantity = Math.min(remaining, chunkLimit)
  383. chunks.push({
  384. address,
  385. quantity: chunkQuantity
  386. })
  387. address += chunkQuantity
  388. remaining -= chunkQuantity
  389. }
  390. return chunks
  391. }
  392. function getReadChunks(functionCode, startAddress, quantity, options = {}) {
  393. const maxQuantity = getMaxReadQuantity(functionCode, options.maxFrameBytes)
  394. return splitQuantity(startAddress, quantity, maxQuantity || quantity)
  395. }
  396. async function sendReadChunk(slaveAddress, functionCode, chunk, label, kind, options = {}) {
  397. if (isBroadcastAddress(slaveAddress)) {
  398. showBroadcastReadAlert(label)
  399. return false
  400. }
  401. return transport.sendManagedFrame(
  402. buildReadFrame(slaveAddress, functionCode, chunk.address, chunk.quantity, {
  403. maxFrameBytes: options.maxFrameBytes
  404. }),
  405. label,
  406. {
  407. address: chunk.address,
  408. functionCode,
  409. kind,
  410. protocol: PROTOCOL_NAME,
  411. quantity: chunk.quantity,
  412. slaveAddress
  413. },
  414. {
  415. maxFrameBytes: options.maxFrameBytes,
  416. showModal: options.showModal
  417. }
  418. )
  419. }
  420. async function readSpans(slaveAddress, functionCode, spans, label, kind, options = {}) {
  421. const readValues = {
  422. coils: {},
  423. words: {}
  424. }
  425. const normalizedSpans = (spans || []).filter((span) => span && span.quantity > 0)
  426. for (const span of normalizedSpans) {
  427. const chunks = getReadChunks(functionCode, span.address, span.quantity, options)
  428. for (const chunk of chunks) {
  429. const responseValue = await sendReadChunk(
  430. slaveAddress,
  431. functionCode,
  432. chunk,
  433. getChunkLabel(label, chunks, chunk),
  434. kind,
  435. options
  436. )
  437. if (!responseValue) return null
  438. if (functionCode === 0x01 || functionCode === 0x02) {
  439. addCoilReadValues(readValues, chunk.address, chunk.quantity, responseValue)
  440. } else {
  441. addWordReadValues(readValues, chunk.address, responseValue)
  442. }
  443. if (typeof options.onChunk === 'function') {
  444. options.onChunk(responseValue, chunk)
  445. }
  446. }
  447. }
  448. return readValues
  449. }
  450. async function readRegisterWords(slaveAddress, functionCode, startAddress, quantity, label, kind, options = {}) {
  451. const words = []
  452. const chunks = getReadChunks(functionCode, startAddress, quantity, options)
  453. for (const chunk of chunks) {
  454. const responseValue = await sendReadChunk(
  455. slaveAddress,
  456. functionCode,
  457. chunk,
  458. getChunkLabel(label, chunks, chunk),
  459. kind,
  460. options
  461. )
  462. if (!responseValue) return null
  463. const chunkWords = responseValue.words || []
  464. chunkWords.forEach((word, index) => {
  465. words[chunk.address - startAddress + index] = Number(word) & 0xFFFF
  466. })
  467. if (typeof options.onChunk === 'function') {
  468. options.onChunk(responseValue, chunk)
  469. }
  470. }
  471. return words
  472. }
  473. async function readBitValues(slaveAddress, functionCode, startAddress, quantity, label, kind, options = {}) {
  474. const result = await readSpans(
  475. slaveAddress,
  476. functionCode,
  477. [{ address: startAddress, quantity }],
  478. label,
  479. kind,
  480. options
  481. )
  482. return result ? result.coils : null
  483. }
  484. async function readSingleHoldingWord(slaveAddress, address, label = '读取配对寄存器', kind = 'holding-word-read') {
  485. const words = await readRegisterWords(slaveAddress, 0x03, address, 1, label, kind)
  486. return words && Number.isInteger(words[0]) ? words[0] & 0xFFFF : null
  487. }
  488. function writeSingleCoil(slaveAddress, address, checked, label, kind = 'coil-write', options = {}) {
  489. const coilValue = checked ? 0xFF00 : 0x0000
  490. return transport.sendManagedFrame(
  491. buildWriteSingleCoilFrame(slaveAddress, address, checked),
  492. label,
  493. isBroadcastAddress(slaveAddress) ? null : {
  494. address,
  495. functionCode: 0x05,
  496. kind,
  497. protocol: PROTOCOL_NAME,
  498. quantity: 1,
  499. slaveAddress,
  500. value: coilValue
  501. },
  502. {
  503. maxFrameBytes: options.maxFrameBytes,
  504. showModal: options.showModal
  505. }
  506. )
  507. }
  508. function writeSingleRegister(slaveAddress, address, value, label, kind = 'register-write', options = {}) {
  509. return transport.sendManagedFrame(
  510. buildWriteSingleRegisterFrame(slaveAddress, address, value),
  511. label,
  512. isBroadcastAddress(slaveAddress) ? null : {
  513. address,
  514. functionCode: 0x06,
  515. kind,
  516. protocol: PROTOCOL_NAME,
  517. quantity: 1,
  518. slaveAddress,
  519. value
  520. },
  521. {
  522. maxFrameBytes: options.maxFrameBytes,
  523. showModal: options.showModal
  524. }
  525. )
  526. }
  527. function writeMultipleRegisters(slaveAddress, address, values, label, kind = 'registers-write', options = {}) {
  528. return transport.sendManagedFrame(
  529. buildWriteMultipleRegistersFrame(slaveAddress, address, values, {
  530. maxFrameBytes: options.maxFrameBytes
  531. }),
  532. label,
  533. isBroadcastAddress(slaveAddress) ? null : {
  534. address,
  535. functionCode: 0x10,
  536. kind,
  537. protocol: PROTOCOL_NAME,
  538. quantity: values.length,
  539. slaveAddress
  540. },
  541. {
  542. maxFrameBytes: options.maxFrameBytes,
  543. showModal: options.showModal
  544. }
  545. )
  546. }
  547. const response = {
  548. MODBUS_EXCEPTION_MESSAGES,
  549. formatExceptionMessage,
  550. getExceptionText,
  551. getExpectedResponseLength,
  552. getReadBufferHint,
  553. isExpectedResponse,
  554. parseModbusRequest,
  555. parseModbusResponse,
  556. readResponseFromBuffer
  557. }
  558. const client = {
  559. getReadChunks,
  560. getSharedSlaveAddress,
  561. getMaxReadQuantity,
  562. readBitValues,
  563. readRegisterWords,
  564. readSingleHoldingWord,
  565. readSpans,
  566. splitQuantity,
  567. getMaxWriteMultipleRegisterQuantity,
  568. writeMultipleRegisters,
  569. writeSingleCoil,
  570. writeSingleRegister
  571. }
  572. module.exports = {
  573. MAX_MODBUS_DMA_BYTES,
  574. MAX_READ_COIL_QUANTITY,
  575. MAX_READ_REGISTER_QUANTITY,
  576. MAX_WRITE_MULTIPLE_REGISTER_QUANTITY,
  577. MODBUS_CRC_OPTIONS,
  578. MODBUS_EXCEPTION_MESSAGES,
  579. PROTOCOL_NAME,
  580. UNLIMITED_FRAME_BYTES,
  581. buildReadFrame,
  582. buildWriteMultipleRegistersFrame,
  583. buildWriteSingleCoilFrame,
  584. buildWriteSingleRegisterFrame,
  585. client,
  586. formatHex,
  587. getMaxReadQuantity,
  588. getMaxWriteMultipleRegisterQuantity,
  589. getReadResponseByteLength,
  590. hasValidCrc16Modbus,
  591. response
  592. }