index.js 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733
  1. const {
  2. padHex
  3. } = require('../../utils/base-utils.js')
  4. const {
  5. bytesToWords,
  6. toByteArray
  7. } = require('../../utils/binary-utils.js')
  8. const {
  9. BYTE_ORDER_HIGH,
  10. appendCrc16Ccitt,
  11. crc16Ccitt,
  12. hasValidCrc16Ccitt
  13. } = require('../../utils/crc.js')
  14. const transport = require('../../transport/ble-core.js')
  15. const PROTOCOL_NAME = 'storage-access'
  16. const CMD_ERR_MASK = 0x80
  17. const CMD_WRITE_MASK = 0x40
  18. const CMD_AREA_MASK = 0x3F
  19. const CMD_INFO = 0x0F
  20. const INFO_DATA_BYTE_LENGTH = 4
  21. const AREA = {
  22. DATA: 0x01,
  23. IDATA: 0x02,
  24. XDATA: 0x03,
  25. CODE: 0x04,
  26. INFO: 0x0F
  27. }
  28. const AREA_NAMES = {
  29. [AREA.DATA]: 'DATA',
  30. [AREA.IDATA]: 'IDATA',
  31. [AREA.XDATA]: 'XDATA',
  32. [AREA.CODE]: 'CODE',
  33. [AREA.INFO]: 'INFO'
  34. }
  35. const AREA_BY_NAME = {
  36. DATA: AREA.DATA,
  37. IDATA: AREA.IDATA,
  38. XDATA: AREA.XDATA,
  39. CODE: AREA.CODE,
  40. INFO: AREA.INFO,
  41. SYNC: AREA.INFO
  42. }
  43. const EXCEPTION_MESSAGES = {
  44. 0x01: '非法命令',
  45. 0x02: '非法区域',
  46. 0x03: '非法地址',
  47. 0x04: '非法长度',
  48. 0x05: '写保护',
  49. 0x06: '设备忙',
  50. 0x07: '格式错误',
  51. 0x08: '访问被拒绝',
  52. 0x09: '内部错误',
  53. 0x0A: '对齐错误',
  54. 0x0B: '范围溢出',
  55. 0x0C: '不支持的操作'
  56. }
  57. const DEFAULT_MAX_FRAME_BYTES = 64
  58. const MAX_PAYLOAD_BYTES = 256
  59. const UNLIMITED_FRAME_BYTES = 0
  60. const READ_REQUEST_LENGTH = 7
  61. const WRITE_REQUEST_OVERHEAD = 7
  62. const READ_RESPONSE_OVERHEAD = 7
  63. const WRITE_RESPONSE_LENGTH = 7
  64. const EXCEPTION_RESPONSE_LENGTH = 4
  65. const INFO_REQUEST_LENGTH = READ_REQUEST_LENGTH
  66. const INFO_RESPONSE_LENGTH = READ_RESPONSE_OVERHEAD + INFO_DATA_BYTE_LENGTH
  67. const STORAGE_CRC_OPTIONS = {
  68. byteOrder: BYTE_ORDER_HIGH
  69. }
  70. const VALID_AREAS = [AREA.DATA, AREA.IDATA, AREA.XDATA, AREA.CODE, AREA.INFO]
  71. const MEMORY_AREAS = [AREA.DATA, AREA.IDATA, AREA.XDATA, AREA.CODE]
  72. function toByte(value, label) {
  73. if (!Number.isInteger(value) || value < 0 || value > 0xFF) {
  74. throw new Error(`${label}必须在 0x00 至 0xFF 之间`)
  75. }
  76. return value
  77. }
  78. function toWord(value, label) {
  79. if (!Number.isInteger(value) || value < 0 || value > 0xFFFF) {
  80. throw new Error(`${label}必须在 0x0000 至 0xFFFF 之间`)
  81. }
  82. return value
  83. }
  84. function normalizeArea(value) {
  85. if (typeof value === 'string') {
  86. const area = AREA_BY_NAME[value.trim().toUpperCase()]
  87. if (area) return area
  88. }
  89. const area = toByte(Number(value), '存储区域')
  90. if (VALID_AREAS.indexOf(area) < 0) {
  91. throw new Error('存储区域必须为 data/idata/xdata/code/info')
  92. }
  93. return area
  94. }
  95. function normalizeMemoryArea(value) {
  96. const area = normalizeArea(value)
  97. if (MEMORY_AREAS.indexOf(area) < 0) {
  98. throw new Error('存储读写区域必须为 data/idata/xdata/code')
  99. }
  100. return area
  101. }
  102. function toByteLength(value, label = '字节长度', maxPayload = MAX_PAYLOAD_BYTES) {
  103. const byteLength = toWord(Number(value), label)
  104. if (byteLength === 0) {
  105. throw new Error(`${label}必须大于 0`)
  106. }
  107. if (maxPayload > 0 && byteLength > maxPayload) {
  108. throw new Error(`单帧最多访问 ${maxPayload} 字节`)
  109. }
  110. return byteLength
  111. }
  112. function splitWord(value) {
  113. return [(value >> 8) & 0xFF, value & 0xFF]
  114. }
  115. function readWord(bytes, offset) {
  116. return (((bytes[offset] || 0) << 8) | (bytes[offset + 1] || 0)) & 0xFFFF
  117. }
  118. function normalizeMaxFrameBytes(maxFrameBytes = DEFAULT_MAX_FRAME_BYTES) {
  119. const numberValue = Number(maxFrameBytes)
  120. if (Number.isFinite(numberValue) && Math.round(numberValue) === UNLIMITED_FRAME_BYTES) return UNLIMITED_FRAME_BYTES
  121. if (Number.isFinite(numberValue) && numberValue > 0) return Math.round(numberValue)
  122. return DEFAULT_MAX_FRAME_BYTES
  123. }
  124. function getPayloadLimitFromFrame(maxFrameBytes, overhead) {
  125. const frameBytes = normalizeMaxFrameBytes(maxFrameBytes)
  126. if (frameBytes === UNLIMITED_FRAME_BYTES) return MAX_PAYLOAD_BYTES
  127. return Math.max(0, Math.min(MAX_PAYLOAD_BYTES, frameBytes - overhead))
  128. }
  129. function getMaxReadByteLength(maxFrameBytes = DEFAULT_MAX_FRAME_BYTES) {
  130. return getPayloadLimitFromFrame(maxFrameBytes, READ_RESPONSE_OVERHEAD)
  131. }
  132. function getMaxWriteByteLength(maxFrameBytes = DEFAULT_MAX_FRAME_BYTES) {
  133. return getPayloadLimitFromFrame(maxFrameBytes, WRITE_REQUEST_OVERHEAD)
  134. }
  135. function buildCommand(area, isWrite = false) {
  136. const normalizedArea = isWrite ? normalizeMemoryArea(area) : normalizeArea(area)
  137. return (isWrite ? CMD_WRITE_MASK : 0x00) | normalizedArea
  138. }
  139. function decodeCommand(command) {
  140. const cmd = toByte(Number(command), '命令字')
  141. return {
  142. area: cmd & CMD_AREA_MASK,
  143. command: cmd,
  144. hasError: !!(cmd & CMD_ERR_MASK),
  145. isWrite: !!(cmd & CMD_WRITE_MASK)
  146. }
  147. }
  148. function hasValidStorageCrc(bytes) {
  149. const frame = toByteArray(bytes)
  150. if (frame.length < 3) return false
  151. if (frame.length >= 4) return hasValidCrc16Ccitt(frame, STORAGE_CRC_OPTIONS)
  152. const expected = crc16Ccitt(frame.slice(0, -2), STORAGE_CRC_OPTIONS)
  153. const received = (((frame[frame.length - 2] || 0) << 8) | (frame[frame.length - 1] || 0)) & 0xFFFF
  154. return expected === received
  155. }
  156. function appendStorageCrc(bytes) {
  157. return appendCrc16Ccitt(bytes, STORAGE_CRC_OPTIONS)
  158. }
  159. function buildReadFrame(area, address, byteLength, options = {}) {
  160. const command = buildCommand(area, false)
  161. const startAddress = toWord(Number(address), '内存地址')
  162. const maxByteLength = getMaxReadByteLength(options.maxFrameBytes)
  163. const length = toByteLength(Number(byteLength), '读取字节长度', maxByteLength || MAX_PAYLOAD_BYTES)
  164. return appendStorageCrc([command].concat(splitWord(startAddress), splitWord(length)))
  165. }
  166. function buildInfoFrame(address = 0, byteLength = INFO_DATA_BYTE_LENGTH) {
  167. return buildReadFrame(AREA.INFO, address, byteLength)
  168. }
  169. function buildWriteFrame(area, address, bytes, options = {}) {
  170. const normalizedArea = normalizeMemoryArea(area)
  171. if (normalizedArea === AREA.CODE) {
  172. throw new Error('code 区暂不支持写入')
  173. }
  174. const command = buildCommand(normalizedArea, true)
  175. const startAddress = toWord(Number(address), '内存地址')
  176. const dataBytes = toByteArray(bytes).map((byte) => toByte(Number(byte), '写入字节'))
  177. const maxByteLength = getMaxWriteByteLength(options.maxFrameBytes)
  178. const length = toByteLength(dataBytes.length, '写入字节长度', maxByteLength || MAX_PAYLOAD_BYTES)
  179. return appendStorageCrc([command].concat(splitWord(startAddress), splitWord(length), dataBytes))
  180. }
  181. function formatHex(bytes) {
  182. return toByteArray(bytes).map((byte) => byte.toString(16).padStart(2, '0').toUpperCase()).join(' ')
  183. }
  184. function parseStorageAccessResponse(bytes) {
  185. const frame = toByteArray(bytes)
  186. if (frame.length < EXCEPTION_RESPONSE_LENGTH || !hasValidStorageCrc(frame)) return null
  187. const command = frame[0] & 0xFF
  188. const decoded = decodeCommand(command)
  189. if (decoded.hasError) {
  190. if (frame.length !== EXCEPTION_RESPONSE_LENGTH) return null
  191. return {
  192. area: decoded.area,
  193. areaName: AREA_NAMES[decoded.area] || 'UNKNOWN',
  194. command,
  195. exceptionCode: frame[1] & 0xFF,
  196. isException: true,
  197. isWrite: decoded.isWrite,
  198. protocol: PROTOCOL_NAME,
  199. sourceCommand: command & ~CMD_ERR_MASK
  200. }
  201. }
  202. if (!AREA_NAMES[decoded.area]) return null
  203. if (decoded.isWrite) {
  204. if (decoded.area === AREA.INFO) return null
  205. if (frame.length !== WRITE_RESPONSE_LENGTH) return null
  206. return {
  207. address: readWord(frame, 1),
  208. area: decoded.area,
  209. areaName: AREA_NAMES[decoded.area] || 'UNKNOWN',
  210. byteLength: readWord(frame, 3),
  211. command,
  212. dataBytes: [],
  213. isException: false,
  214. isWrite: true,
  215. protocol: PROTOCOL_NAME
  216. }
  217. }
  218. if (frame.length < READ_RESPONSE_OVERHEAD) return null
  219. const byteLength = readWord(frame, 3)
  220. const dataStart = 5
  221. const dataEnd = dataStart + byteLength
  222. if (frame.length !== dataEnd + 2) return null
  223. const dataBytes = frame.slice(dataStart, dataEnd)
  224. return {
  225. address: readWord(frame, 1),
  226. area: decoded.area,
  227. areaName: AREA_NAMES[decoded.area] || 'UNKNOWN',
  228. byteLength,
  229. command,
  230. dataBytes,
  231. isException: false,
  232. isInfo: decoded.area === AREA.INFO,
  233. isWrite: false,
  234. protocol: PROTOCOL_NAME,
  235. words: bytesToWords(dataBytes.length % 2 === 0 ? dataBytes : dataBytes.concat(0)),
  236. ...(decoded.area === AREA.INFO && dataBytes.length >= INFO_DATA_BYTE_LENGTH
  237. ? {
  238. codeInfoAddress: readWord(frame, 5),
  239. codeInfoByteLength: readWord(frame, 7),
  240. infoBytes: dataBytes.slice(0, INFO_DATA_BYTE_LENGTH)
  241. }
  242. : {})
  243. }
  244. }
  245. function parseStorageAccessRequest(bytes) {
  246. const frame = toByteArray(bytes)
  247. if (frame.length < INFO_REQUEST_LENGTH || !hasValidStorageCrc(frame)) return null
  248. const command = frame[0] & 0xFF
  249. if (frame.length < READ_REQUEST_LENGTH) return null
  250. const decoded = decodeCommand(command)
  251. if (decoded.hasError) return null
  252. if (!AREA_NAMES[decoded.area]) return null
  253. if (decoded.area === AREA.INFO && decoded.isWrite) return null
  254. const address = readWord(frame, 1)
  255. const byteLength = readWord(frame, 3)
  256. const expectedLength = decoded.isWrite
  257. ? WRITE_REQUEST_OVERHEAD + byteLength
  258. : READ_REQUEST_LENGTH
  259. if (byteLength <= 0 || frame.length !== expectedLength) return null
  260. return {
  261. address,
  262. area: decoded.area,
  263. areaName: AREA_NAMES[decoded.area] || 'UNKNOWN',
  264. byteLength,
  265. command,
  266. dataBytes: decoded.isWrite ? frame.slice(5, 5 + byteLength) : [],
  267. isInfo: decoded.area === AREA.INFO,
  268. isWrite: decoded.isWrite,
  269. kind: 'raw-hex',
  270. operation: decoded.isWrite ? 'write' : 'read',
  271. protocol: PROTOCOL_NAME,
  272. quantity: byteLength
  273. }
  274. }
  275. function getExpectedResponseLength(expected, responseCommand, responseBytes = []) {
  276. if (!expected) return 0
  277. const command = Number(responseCommand) & 0xFF
  278. if (command === (expected.command | CMD_ERR_MASK)) return EXCEPTION_RESPONSE_LENGTH
  279. if (command !== expected.command) return 0
  280. if (expected.operation === 'write' || expected.isWrite) {
  281. return WRITE_RESPONSE_LENGTH
  282. }
  283. if (responseBytes.length < 5) return 0
  284. return READ_RESPONSE_OVERHEAD + readWord(responseBytes, 3)
  285. }
  286. function isExpectedResponse(response, expected) {
  287. if (!response || !expected) return false
  288. const sourceCommand = response.isException ? response.sourceCommand : response.command
  289. if (sourceCommand !== expected.command) return false
  290. if (!response.isException && response.area !== expected.area) return false
  291. if (response.isException) return true
  292. if (response.address !== expected.address) return false
  293. if (response.byteLength !== expected.byteLength) return false
  294. if (!response.isWrite && (!Array.isArray(response.dataBytes) || response.dataBytes.length !== expected.byteLength)) return false
  295. return true
  296. }
  297. function getExceptionText(code) {
  298. return EXCEPTION_MESSAGES[code] || '未知异常'
  299. }
  300. function formatExceptionMessage(response) {
  301. const sourceCommand = response && response.sourceCommand
  302. const exceptionCode = response && response.exceptionCode
  303. const exceptionText = getExceptionText(exceptionCode)
  304. return `设备返回异常帧:命令 0x${padHex(sourceCommand, 2)},异常码 0x${padHex(exceptionCode, 2)}(${exceptionText})`
  305. }
  306. function getReadBufferHint(expected) {
  307. if (!expected) return 0
  308. if (expected.operation === 'write' || expected.isWrite) return WRITE_RESPONSE_LENGTH
  309. return READ_RESPONSE_OVERHEAD + Number(expected.byteLength || expected.quantity || 0)
  310. }
  311. function alignResponseBuffer(buffer, expected) {
  312. if (!Array.isArray(buffer) || !buffer.length || !expected) return
  313. const expectedCommands = [expected.command, expected.command | CMD_ERR_MASK]
  314. let matchIndex = -1
  315. for (let index = 0; index < buffer.length; index += 1) {
  316. if (expectedCommands.indexOf(buffer[index]) < 0) continue
  317. matchIndex = index
  318. break
  319. }
  320. if (matchIndex > 0) {
  321. buffer.splice(0, matchIndex)
  322. } else if (matchIndex < 0 && buffer.length > 1) {
  323. buffer.splice(0, buffer.length - 1)
  324. }
  325. }
  326. function readResponseFromBuffer(buffer, expected, options = {}) {
  327. if (!Array.isArray(buffer) || !buffer.length || !expected) {
  328. return {
  329. status: 'pending'
  330. }
  331. }
  332. alignResponseBuffer(buffer, expected)
  333. while (buffer.length >= 1) {
  334. const responseCommand = buffer[0]
  335. const responseLength = getExpectedResponseLength(expected, responseCommand, buffer)
  336. if (!responseLength) {
  337. return {
  338. status: 'pending'
  339. }
  340. }
  341. const frameLimit = normalizeMaxFrameBytes(
  342. options.maxFrameBytes === undefined ? expected.maxFrameBytes : options.maxFrameBytes
  343. )
  344. if (frameLimit > 0 && responseLength > frameLimit) {
  345. return {
  346. frameLimit,
  347. responseLength,
  348. status: 'frame-too-long'
  349. }
  350. }
  351. if (buffer.length < responseLength) {
  352. return {
  353. status: 'pending'
  354. }
  355. }
  356. const frameBytes = buffer.slice(0, responseLength)
  357. const response = parseStorageAccessResponse(frameBytes)
  358. if (!response) {
  359. return {
  360. frameBytes,
  361. status: 'invalid'
  362. }
  363. }
  364. if (!isExpectedResponse(response, expected)) {
  365. buffer.shift()
  366. alignResponseBuffer(buffer, expected)
  367. continue
  368. }
  369. if (response.isException) {
  370. return {
  371. message: formatExceptionMessage(response),
  372. response,
  373. status: 'exception'
  374. }
  375. }
  376. buffer.splice(0, responseLength)
  377. return {
  378. response,
  379. status: 'complete'
  380. }
  381. }
  382. return {
  383. status: 'pending'
  384. }
  385. }
  386. function createExpected(area, address, byteLength, isWrite, kind) {
  387. const normalizedArea = normalizeMemoryArea(area)
  388. const command = buildCommand(normalizedArea, isWrite)
  389. return {
  390. address,
  391. area: normalizedArea,
  392. byteLength,
  393. command,
  394. isWrite,
  395. kind,
  396. operation: isWrite ? 'write' : 'read',
  397. protocol: PROTOCOL_NAME,
  398. quantity: byteLength
  399. }
  400. }
  401. function createInfoExpected(kind = 'storage-info-read') {
  402. return {
  403. address: 0,
  404. area: AREA.INFO,
  405. byteLength: INFO_DATA_BYTE_LENGTH,
  406. command: buildCommand(AREA.INFO, false),
  407. frame: buildInfoFrame(0, INFO_DATA_BYTE_LENGTH),
  408. isInfo: true,
  409. isWrite: false,
  410. kind,
  411. operation: 'read',
  412. protocol: PROTOCOL_NAME
  413. }
  414. }
  415. function formatAddress(value) {
  416. return Number(value || 0).toString(16).toUpperCase()
  417. }
  418. function getChunkLabel(label, chunks, chunk) {
  419. if (!label || chunks.length <= 1) return label
  420. return `${label} ${formatAddress(chunk.address)}-${formatAddress(chunk.address + chunk.quantity - 1)}`
  421. }
  422. function splitQuantity(startAddress, quantity, maxQuantity) {
  423. const chunks = []
  424. let address = Number(startAddress) || 0
  425. let remaining = Math.max(0, Math.floor(Number(quantity) || 0))
  426. const chunkLimit = Math.max(1, Math.floor(Number(maxQuantity) || remaining || 1))
  427. while (remaining > 0) {
  428. const chunkQuantity = Math.min(remaining, chunkLimit)
  429. chunks.push({
  430. address,
  431. quantity: chunkQuantity
  432. })
  433. address += chunkQuantity
  434. remaining -= chunkQuantity
  435. }
  436. return chunks
  437. }
  438. function getReadChunks(startAddress, byteLength, options = {}) {
  439. const maxByteLength = getMaxReadByteLength(options.maxFrameBytes)
  440. return splitQuantity(startAddress, byteLength, maxByteLength || byteLength)
  441. }
  442. function getWriteChunks(startAddress, bytes, options = {}) {
  443. const sourceBytes = Array.prototype.slice.call(bytes || []).map((byte) => Number(byte) & 0xFF)
  444. const maxByteLength = getMaxWriteByteLength(options.maxFrameBytes)
  445. const chunks = splitQuantity(startAddress, sourceBytes.length, maxByteLength || sourceBytes.length)
  446. let offset = 0
  447. return chunks.map((chunk) => {
  448. const dataBytes = sourceBytes.slice(offset, offset + chunk.quantity)
  449. offset += chunk.quantity
  450. return {
  451. ...chunk,
  452. dataBytes
  453. }
  454. })
  455. }
  456. function sendReadChunk(area, chunk, label, kind, options = {}) {
  457. const normalizedArea = normalizeMemoryArea(area)
  458. return transport.sendManagedFrame(
  459. buildReadFrame(normalizedArea, chunk.address, chunk.quantity, {
  460. maxFrameBytes: options.maxFrameBytes
  461. }),
  462. label,
  463. createExpected(normalizedArea, chunk.address, chunk.quantity, false, kind),
  464. {
  465. maxFrameBytes: options.maxFrameBytes,
  466. showModal: options.showModal
  467. }
  468. )
  469. }
  470. async function readMemory(area, startAddress, byteLength, label, kind = 'storage-memory-read', options = {}) {
  471. const normalizedArea = normalizeMemoryArea(area)
  472. const bytes = []
  473. const chunks = getReadChunks(startAddress, byteLength, options)
  474. for (const chunk of chunks) {
  475. const response = await sendReadChunk(
  476. normalizedArea,
  477. chunk,
  478. getChunkLabel(label, chunks, chunk),
  479. kind,
  480. options
  481. )
  482. if (!response) return null
  483. const dataBytes = Array.isArray(response.dataBytes) ? response.dataBytes : []
  484. dataBytes.forEach((byte, index) => {
  485. bytes[chunk.address - startAddress + index] = Number(byte) & 0xFF
  486. })
  487. if (typeof options.onChunk === 'function') {
  488. options.onChunk(response, chunk)
  489. }
  490. }
  491. return bytes
  492. }
  493. async function writeMemory(area, startAddress, bytes, label, kind = 'storage-memory-write', options = {}) {
  494. const normalizedArea = normalizeMemoryArea(area)
  495. const chunks = getWriteChunks(startAddress, bytes, options)
  496. for (const chunk of chunks) {
  497. const response = await transport.sendManagedFrame(
  498. buildWriteFrame(normalizedArea, chunk.address, chunk.dataBytes, {
  499. maxFrameBytes: options.maxFrameBytes
  500. }),
  501. getChunkLabel(label, chunks, chunk),
  502. createExpected(normalizedArea, chunk.address, chunk.quantity, true, kind),
  503. {
  504. maxFrameBytes: options.maxFrameBytes,
  505. showModal: options.showModal
  506. }
  507. )
  508. if (!response) return false
  509. if (typeof options.onChunk === 'function') {
  510. options.onChunk(response, chunk)
  511. }
  512. }
  513. return true
  514. }
  515. async function readCodeInfoBlock(label = '同步info', kind = 'storage-info-read', options = {}) {
  516. const infoResponse = await transport.sendManagedFrame(
  517. buildInfoFrame(0, INFO_DATA_BYTE_LENGTH),
  518. label,
  519. createInfoExpected(`${kind}-info`),
  520. {
  521. maxFrameBytes: options.maxFrameBytes,
  522. showModal: options.showModal
  523. }
  524. )
  525. if (!infoResponse) return null
  526. const codeInfoAddress = Number(infoResponse.codeInfoAddress || 0)
  527. const codeInfoByteLength = Number(infoResponse.codeInfoByteLength || 0)
  528. if (!codeInfoByteLength || codeInfoByteLength > 0xFFFF) {
  529. transport.showCommandAlert(label, 'info 信息块长度无效')
  530. return null
  531. }
  532. const bytes = await readMemory(
  533. AREA.CODE,
  534. codeInfoAddress,
  535. codeInfoByteLength,
  536. label,
  537. kind,
  538. options
  539. )
  540. if (!bytes) return null
  541. return {
  542. codeInfoAddress,
  543. codeInfoByteLength,
  544. codeInfoBytes: bytes,
  545. infoBytes: Array.isArray(infoResponse.infoBytes)
  546. ? infoResponse.infoBytes
  547. : [],
  548. codeInfoMemoryType: AREA.CODE
  549. }
  550. }
  551. const response = {
  552. createExpected,
  553. createInfoExpected,
  554. formatExceptionMessage,
  555. getExceptionText,
  556. getExpectedResponseLength,
  557. getReadBufferHint,
  558. isExpectedResponse,
  559. parseStorageAccessRequest,
  560. parseStorageAccessResponse,
  561. readResponseFromBuffer
  562. }
  563. const client = {
  564. AREA,
  565. getMaxReadByteLength,
  566. getMaxWriteByteLength,
  567. getReadChunks,
  568. getWriteChunks,
  569. readCodeInfoBlock,
  570. readMemory,
  571. splitQuantity,
  572. writeMemory
  573. }
  574. module.exports = {
  575. AREA,
  576. AREA_BY_NAME,
  577. AREA_NAMES,
  578. CMD_AREA_MASK,
  579. CMD_ERR_MASK,
  580. CMD_INFO,
  581. CMD_WRITE_MASK,
  582. DEFAULT_MAX_FRAME_BYTES,
  583. EXCEPTION_MESSAGES,
  584. EXCEPTION_RESPONSE_LENGTH,
  585. INFO_REQUEST_LENGTH,
  586. INFO_RESPONSE_LENGTH,
  587. INFO_DATA_BYTE_LENGTH,
  588. MAX_PAYLOAD_BYTES,
  589. PROTOCOL_NAME,
  590. READ_REQUEST_LENGTH,
  591. READ_RESPONSE_OVERHEAD,
  592. STORAGE_CRC_OPTIONS,
  593. UNLIMITED_FRAME_BYTES,
  594. WRITE_REQUEST_OVERHEAD,
  595. WRITE_RESPONSE_LENGTH,
  596. appendStorageCrc,
  597. buildCommand,
  598. buildInfoFrame,
  599. buildReadFrame,
  600. buildWriteFrame,
  601. client,
  602. decodeCommand,
  603. formatHex,
  604. getMaxReadByteLength,
  605. getMaxWriteByteLength,
  606. hasValidStorageCrc,
  607. normalizeArea,
  608. normalizeMaxFrameBytes,
  609. normalizeMemoryArea,
  610. response,
  611. splitWord,
  612. toByte,
  613. toByteLength,
  614. toWord
  615. }