client.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424
  1. const {
  2. buildCodeInfoQueryFrame,
  3. buildDebugReadMemoryFrame,
  4. buildDebugWriteMemoryFrame,
  5. buildReadFrame,
  6. buildWriteMultipleRegistersFrame,
  7. buildWriteSingleCoilFrame,
  8. buildWriteSingleRegisterFrame,
  9. getMaxDebugReadByteLength,
  10. getMaxDebugWriteByteLength,
  11. getMaxReadQuantity,
  12. getMaxWriteMultipleRegisterQuantity
  13. } = require('./frame.js')
  14. const settingsService = require('../../store/settings-store.js')
  15. const transport = require('../../transport/ble-core.js')
  16. const {
  17. addCoilReadValues,
  18. addWordReadValues
  19. } = require('../../utils/register-value-utils.js')
  20. function getSharedSlaveAddress(title = '从机地址错误') {
  21. try {
  22. return settingsService.getModbusSlaveAddress()
  23. } catch (error) {
  24. transport.showCommandAlert(title, error.message)
  25. return null
  26. }
  27. }
  28. function formatAddress(value) {
  29. return Number(value || 0).toString(16).toUpperCase()
  30. }
  31. function getChunkLabel(label, chunks, chunk) {
  32. if (!label || chunks.length <= 1) return label
  33. return `${label} ${formatAddress(chunk.address)}-${formatAddress(chunk.address + chunk.quantity - 1)}`
  34. }
  35. function isBroadcastAddress(slaveAddress) {
  36. return Number(slaveAddress) === 0
  37. }
  38. function showBroadcastReadAlert(label) {
  39. transport.showCommandAlert(label || '读取命令错误', '广播地址 0x00 不支持读取')
  40. }
  41. function splitQuantity(startAddress, quantity, maxQuantity) {
  42. const chunks = []
  43. let address = Number(startAddress) || 0
  44. let remaining = Math.max(0, Math.floor(Number(quantity) || 0))
  45. const chunkLimit = Math.max(1, Math.floor(Number(maxQuantity) || remaining || 1))
  46. while (remaining > 0) {
  47. const chunkQuantity = Math.min(remaining, chunkLimit)
  48. chunks.push({
  49. address,
  50. quantity: chunkQuantity
  51. })
  52. address += chunkQuantity
  53. remaining -= chunkQuantity
  54. }
  55. return chunks
  56. }
  57. function getReadChunks(functionCode, startAddress, quantity, options = {}) {
  58. const maxQuantity = getMaxReadQuantity(functionCode, options.maxFrameBytes)
  59. return splitQuantity(startAddress, quantity, maxQuantity || quantity)
  60. }
  61. function getDebugReadChunks(startAddress, byteLength, options = {}) {
  62. const maxByteLength = getMaxDebugReadByteLength(options.maxFrameBytes)
  63. return splitQuantity(startAddress, byteLength, maxByteLength || byteLength)
  64. }
  65. function getDebugWriteChunks(startAddress, bytes, options = {}) {
  66. const sourceBytes = Array.prototype.slice.call(bytes || [])
  67. const maxByteLength = getMaxDebugWriteByteLength(options.maxFrameBytes)
  68. const chunks = splitQuantity(startAddress, sourceBytes.length, maxByteLength || sourceBytes.length)
  69. let offset = 0
  70. return chunks.map((chunk) => {
  71. const dataBytes = sourceBytes.slice(offset, offset + chunk.quantity)
  72. offset += chunk.quantity
  73. return {
  74. ...chunk,
  75. dataBytes
  76. }
  77. })
  78. }
  79. async function sendReadChunk(slaveAddress, functionCode, chunk, label, kind, options = {}) {
  80. if (isBroadcastAddress(slaveAddress)) {
  81. showBroadcastReadAlert(label)
  82. return false
  83. }
  84. return transport.sendManagedFrame(
  85. buildReadFrame(slaveAddress, functionCode, chunk.address, chunk.quantity, {
  86. maxFrameBytes: options.maxFrameBytes
  87. }),
  88. label,
  89. {
  90. address: chunk.address,
  91. functionCode,
  92. kind,
  93. quantity: chunk.quantity,
  94. slaveAddress
  95. },
  96. {
  97. maxFrameBytes: options.maxFrameBytes,
  98. showModal: options.showModal
  99. }
  100. )
  101. }
  102. async function sendDebugReadChunk(slaveAddress, memoryType, chunk, label, kind, options = {}) {
  103. if (isBroadcastAddress(slaveAddress)) {
  104. showBroadcastReadAlert(label)
  105. return false
  106. }
  107. return transport.sendManagedFrame(
  108. buildDebugReadMemoryFrame(slaveAddress, memoryType, chunk.address, chunk.quantity, {
  109. maxFrameBytes: options.maxFrameBytes
  110. }),
  111. label,
  112. {
  113. address: chunk.address,
  114. functionCode: 0x41,
  115. kind,
  116. memoryType,
  117. quantity: chunk.quantity,
  118. slaveAddress
  119. },
  120. {
  121. maxFrameBytes: options.maxFrameBytes,
  122. showModal: options.showModal
  123. }
  124. )
  125. }
  126. async function readSpans(slaveAddress, functionCode, spans, label, kind, options = {}) {
  127. const readValues = {
  128. coils: {},
  129. words: {}
  130. }
  131. const normalizedSpans = (spans || []).filter((span) => span && span.quantity > 0)
  132. for (const span of normalizedSpans) {
  133. const chunks = getReadChunks(functionCode, span.address, span.quantity, options)
  134. for (const chunk of chunks) {
  135. const response = await sendReadChunk(
  136. slaveAddress,
  137. functionCode,
  138. chunk,
  139. getChunkLabel(label, chunks, chunk),
  140. kind,
  141. options
  142. )
  143. if (!response) return null
  144. if (functionCode === 0x01 || functionCode === 0x02) {
  145. addCoilReadValues(readValues, chunk.address, chunk.quantity, response)
  146. } else {
  147. addWordReadValues(readValues, chunk.address, response)
  148. }
  149. if (typeof options.onChunk === 'function') {
  150. options.onChunk(response, chunk)
  151. }
  152. }
  153. }
  154. return readValues
  155. }
  156. async function readRegisterWords(slaveAddress, functionCode, startAddress, quantity, label, kind, options = {}) {
  157. const words = []
  158. const chunks = getReadChunks(functionCode, startAddress, quantity, options)
  159. for (const chunk of chunks) {
  160. const response = await sendReadChunk(
  161. slaveAddress,
  162. functionCode,
  163. chunk,
  164. getChunkLabel(label, chunks, chunk),
  165. kind,
  166. options
  167. )
  168. if (!response) return null
  169. const chunkWords = response.words || []
  170. chunkWords.forEach((word, index) => {
  171. words[chunk.address - startAddress + index] = Number(word) & 0xFFFF
  172. })
  173. if (typeof options.onChunk === 'function') {
  174. options.onChunk(response, chunk)
  175. }
  176. }
  177. return words
  178. }
  179. async function readDebugMemory(slaveAddress, memoryType, startAddress, byteLength, label, kind = 'debug-memory-read', options = {}) {
  180. const bytes = []
  181. const chunks = getDebugReadChunks(startAddress, byteLength, options)
  182. for (const chunk of chunks) {
  183. const response = await sendDebugReadChunk(
  184. slaveAddress,
  185. memoryType,
  186. chunk,
  187. getChunkLabel(label, chunks, chunk),
  188. kind,
  189. options
  190. )
  191. if (!response) return null
  192. const dataBytes = Array.isArray(response.dataBytes) ? response.dataBytes : []
  193. dataBytes.forEach((byte, index) => {
  194. bytes[chunk.address - startAddress + index] = Number(byte) & 0xFF
  195. })
  196. if (typeof options.onChunk === 'function') {
  197. options.onChunk(response, chunk)
  198. }
  199. }
  200. return bytes
  201. }
  202. async function queryCodeInfoBlock(slaveAddress, label = '查询Code信息块', kind = 'code-info-query', options = {}) {
  203. if (isBroadcastAddress(slaveAddress)) {
  204. showBroadcastReadAlert(label)
  205. return null
  206. }
  207. const response = await transport.sendManagedFrame(
  208. buildCodeInfoQueryFrame(slaveAddress),
  209. label,
  210. {
  211. functionCode: 0x40,
  212. kind,
  213. quantity: 1,
  214. slaveAddress
  215. },
  216. {
  217. maxFrameBytes: options.maxFrameBytes,
  218. showModal: options.showModal
  219. }
  220. )
  221. if (!response) return null
  222. return {
  223. address: response.codeInfoAddress,
  224. byteLength: response.codeInfoLength,
  225. memoryType: response.memoryType
  226. }
  227. }
  228. async function readCodeInfoBlock(slaveAddress, label = '读取Code信息块', kind = 'code-info-read', options = {}) {
  229. const info = await queryCodeInfoBlock(slaveAddress, label, 'code-info-query', options)
  230. if (!info) return null
  231. if (info.memoryType !== 0x03) {
  232. transport.showCommandAlert(label, '设备返回的信息块不在 code 区')
  233. return null
  234. }
  235. const bytes = await readDebugMemory(
  236. slaveAddress,
  237. 0x03,
  238. info.address,
  239. info.byteLength,
  240. label,
  241. kind,
  242. options
  243. )
  244. if (!bytes) return null
  245. return {
  246. ...info,
  247. bytes
  248. }
  249. }
  250. async function readBitValues(slaveAddress, functionCode, startAddress, quantity, label, kind, options = {}) {
  251. const result = await readSpans(
  252. slaveAddress,
  253. functionCode,
  254. [{ address: startAddress, quantity }],
  255. label,
  256. kind,
  257. options
  258. )
  259. return result ? result.coils : null
  260. }
  261. async function readSingleHoldingWord(slaveAddress, address, label = '读取配对寄存器', kind = 'holding-word-read') {
  262. const words = await readRegisterWords(slaveAddress, 0x03, address, 1, label, kind)
  263. return words && Number.isInteger(words[0]) ? words[0] & 0xFFFF : null
  264. }
  265. function writeSingleCoil(slaveAddress, address, checked, label, kind = 'coil-write', options = {}) {
  266. const coilValue = checked ? 0xFF00 : 0x0000
  267. return transport.sendManagedFrame(
  268. buildWriteSingleCoilFrame(slaveAddress, address, checked),
  269. label,
  270. isBroadcastAddress(slaveAddress) ? null : {
  271. address,
  272. functionCode: 0x05,
  273. kind,
  274. quantity: 1,
  275. slaveAddress,
  276. value: coilValue
  277. },
  278. {
  279. maxFrameBytes: options.maxFrameBytes,
  280. showModal: options.showModal
  281. }
  282. )
  283. }
  284. function writeSingleRegister(slaveAddress, address, value, label, kind = 'register-write', options = {}) {
  285. return transport.sendManagedFrame(
  286. buildWriteSingleRegisterFrame(slaveAddress, address, value),
  287. label,
  288. isBroadcastAddress(slaveAddress) ? null : {
  289. address,
  290. functionCode: 0x06,
  291. kind,
  292. quantity: 1,
  293. slaveAddress,
  294. value
  295. },
  296. {
  297. maxFrameBytes: options.maxFrameBytes,
  298. showModal: options.showModal
  299. }
  300. )
  301. }
  302. function writeMultipleRegisters(slaveAddress, address, values, label, kind = 'registers-write', options = {}) {
  303. return transport.sendManagedFrame(
  304. buildWriteMultipleRegistersFrame(slaveAddress, address, values, {
  305. maxFrameBytes: options.maxFrameBytes
  306. }),
  307. label,
  308. isBroadcastAddress(slaveAddress) ? null : {
  309. address,
  310. functionCode: 0x10,
  311. kind,
  312. quantity: values.length,
  313. slaveAddress
  314. },
  315. {
  316. maxFrameBytes: options.maxFrameBytes,
  317. showModal: options.showModal
  318. }
  319. )
  320. }
  321. async function writeDebugMemory(slaveAddress, memoryType, startAddress, bytes, label, kind = 'debug-memory-write', options = {}) {
  322. const dataBytes = Array.prototype.slice.call(bytes || []).map((byte) => Number(byte) & 0xFF)
  323. const chunks = getDebugWriteChunks(startAddress, dataBytes, options)
  324. for (const chunk of chunks) {
  325. const response = await transport.sendManagedFrame(
  326. buildDebugWriteMemoryFrame(slaveAddress, memoryType, chunk.address, chunk.dataBytes, {
  327. maxFrameBytes: options.maxFrameBytes
  328. }),
  329. getChunkLabel(label, chunks, chunk),
  330. isBroadcastAddress(slaveAddress) ? null : {
  331. address: chunk.address,
  332. functionCode: 0x42,
  333. kind,
  334. memoryType,
  335. quantity: chunk.quantity,
  336. slaveAddress
  337. },
  338. {
  339. maxFrameBytes: options.maxFrameBytes,
  340. showModal: options.showModal
  341. }
  342. )
  343. if (!response) return false
  344. if (typeof options.onChunk === 'function') {
  345. options.onChunk(response, chunk)
  346. }
  347. }
  348. return true
  349. }
  350. module.exports = {
  351. getDebugReadChunks,
  352. getDebugWriteChunks,
  353. getReadChunks,
  354. getSharedSlaveAddress,
  355. getMaxReadQuantity,
  356. queryCodeInfoBlock,
  357. readBitValues,
  358. readCodeInfoBlock,
  359. readDebugMemory,
  360. readRegisterWords,
  361. readSingleHoldingWord,
  362. readSpans,
  363. splitQuantity,
  364. getMaxWriteMultipleRegisterQuantity,
  365. writeDebugMemory,
  366. writeMultipleRegisters,
  367. writeSingleCoil,
  368. writeSingleRegister
  369. }