bootloader-service.js 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806
  1. const transport = require('./ble-transport')
  2. const {
  3. BYTE_ORDER_HIGH,
  4. crc16Ccitt,
  5. appendCrc16Ccitt,
  6. hasValidCrc16Ccitt
  7. } = require('./crc')
  8. const {
  9. BOOTLOADER_HEAD
  10. } = require('./bootloader-frame')
  11. const {
  12. softReset
  13. } = require('./motor-control-protocol')
  14. const {
  15. delay
  16. } = require('./base-utils')
  17. const {
  18. formatBytes,
  19. isCancelError,
  20. loadSelectedFile
  21. } = require('./file-service')
  22. const HEAD = BOOTLOADER_HEAD
  23. const BOOTLOADER_CRC_OPTIONS = {
  24. byteOrder: BYTE_ORDER_HIGH
  25. }
  26. const ACK = 0x06
  27. const NAK = 0x15
  28. const HANDSHAKE_INTERVAL_MS = 200
  29. const HANDSHAKE_ATTEMPTS = 10
  30. const HANDSHAKE_TIMEOUT_MS = HANDSHAKE_INTERVAL_MS * HANDSHAKE_ATTEMPTS
  31. const RESPONSE_TIMEOUT_MS = 3000
  32. const PROGRAM_RESPONSE_TIMEOUT_MS = 6000
  33. const PROGRAM_CHUNK_SIZE = 128
  34. const FILE_SIZES = {
  35. 16: 16 * 1024,
  36. 32: 32 * 1024
  37. }
  38. const FLASH_LAYOUTS = {
  39. 16: {
  40. capacity: FILE_SIZES[16],
  41. startAddress: 0x0400,
  42. endAddress: 0x4000
  43. },
  44. 32: {
  45. capacity: FILE_SIZES[32],
  46. startAddress: 0x0800,
  47. endAddress: 0x8000
  48. }
  49. }
  50. const FLASH_SIZE_TEXT = Object.keys(FILE_SIZES)
  51. .map((sizeKb) => formatBytes(FILE_SIZES[sizeKb]))
  52. .join(' 或 ')
  53. const CHIP_FLASH_SIZE_KB = {
  54. FU6572L: 32,
  55. FU6572N: 32,
  56. FU6572T: 32,
  57. FU6565N: 32,
  58. FU6565T: 32,
  59. FU6563N: 32,
  60. FU6562L: 32,
  61. FU6562LA: 32,
  62. FU6562Q: 32,
  63. FU6562S: 32,
  64. FU6562T: 32,
  65. FU6532N: 32,
  66. FU6532T: 32,
  67. FU6522L: 32,
  68. FU6522N: 32,
  69. FU6522T: 32,
  70. FU6812L2: 16,
  71. FU6812N2: 16,
  72. FU6812S2: 16,
  73. FU6812V: 16,
  74. FU6861Q2: 16,
  75. FU6861N2: 16,
  76. FU6861NF2: 16,
  77. FU6861L2: 16,
  78. FU6862L: 16,
  79. FU6862Q: 16,
  80. FU6872P: 16
  81. }
  82. const CHIP_FAMILY_FLASH_SIZE_KB = {
  83. 65: 32,
  84. 68: 16
  85. }
  86. const state = {
  87. bootloaderChipId: '--',
  88. bootloaderDetailText: '',
  89. bootloaderProgress: 0,
  90. bootloaderStatusText: '',
  91. bootloaderVersion: '--',
  92. chipModel: '--',
  93. deviceProgramCrcText: '--',
  94. firmwareChecksumText: '--',
  95. firmwareName: '',
  96. firmwareSize: 0,
  97. firmwareSizeText: '--',
  98. firmwareValidText: '未选择',
  99. isBootloaderBusy: false,
  100. isFirmwareReady: false
  101. }
  102. let firmwareBytes = null
  103. let initialized = false
  104. let unsubscribeTransport = null
  105. let activeResponseWaiter = null
  106. const subscribers = []
  107. function getState() {
  108. return {
  109. ...state
  110. }
  111. }
  112. function setState(changedData) {
  113. Object.assign(state, changedData)
  114. subscribers.slice().forEach((subscriber) => {
  115. subscriber(getState())
  116. })
  117. }
  118. function abortActiveResponseWaiter(message) {
  119. if (!activeResponseWaiter) return false
  120. const waiter = activeResponseWaiter
  121. activeResponseWaiter = null
  122. waiter.abort(new Error(message || '蓝牙已断开'))
  123. return true
  124. }
  125. function subscribe(subscriber) {
  126. if (typeof subscriber !== 'function') return () => {}
  127. subscribers.push(subscriber)
  128. subscriber(getState())
  129. return () => {
  130. const index = subscribers.indexOf(subscriber)
  131. if (index >= 0) subscribers.splice(index, 1)
  132. }
  133. }
  134. function init() {
  135. transport.init()
  136. if (initialized) return
  137. unsubscribeTransport = transport.subscribe((transportState) => {
  138. if (!transportState.connectedDevice) {
  139. abortActiveResponseWaiter('蓝牙已断开')
  140. }
  141. if (!transportState.connectedDevice && state.isBootloaderBusy) {
  142. transport.showCommandAlert('BootLoader', '蓝牙已断开,升级已停止')
  143. setState({
  144. bootloaderDetailText: '蓝牙已断开,升级已停止',
  145. bootloaderStatusText: '升级失败',
  146. isBootloaderBusy: false
  147. })
  148. }
  149. })
  150. initialized = true
  151. }
  152. function toHex(value, length = 2) {
  153. return Number(value || 0).toString(16).toUpperCase().padStart(length, '0')
  154. }
  155. function formatCrc(value) {
  156. return `0x${toHex(value, 4)}`
  157. }
  158. function normalizeModel(value) {
  159. const text = String(value || '').trim()
  160. return text && text !== '--' ? text : ''
  161. }
  162. function extractChipModels(chipModel) {
  163. const model = normalizeModel(chipModel).toUpperCase()
  164. return model.match(/FU\d{4}[A-Z0-9]*/g) || []
  165. }
  166. function inferFlashSizeKb(chipModel) {
  167. const models = extractChipModels(chipModel)
  168. for (const model of models) {
  169. if (CHIP_FLASH_SIZE_KB[model]) return CHIP_FLASH_SIZE_KB[model]
  170. const family = model.slice(2, 4)
  171. if (CHIP_FAMILY_FLASH_SIZE_KB[family]) return CHIP_FAMILY_FLASH_SIZE_KB[family]
  172. }
  173. const text = normalizeModel(chipModel).toUpperCase()
  174. if (/(^|[^0-9])32\s*K(B)?([^0-9]|$)/.test(text)) return 32
  175. if (/(^|[^0-9])16\s*K(B)?([^0-9]|$)/.test(text)) return 16
  176. return null
  177. }
  178. function inferFlashSizeKbFromBytes(byteLength) {
  179. return Number(Object.keys(FILE_SIZES).find((sizeKb) => FILE_SIZES[sizeKb] === byteLength)) || null
  180. }
  181. function inferUpgradeFlashSizeKb(chipModel, byteLength) {
  182. return inferFlashSizeKb(chipModel) || inferFlashSizeKbFromBytes(byteLength)
  183. }
  184. function getFlashLayout() {
  185. const sizeKb = inferUpgradeFlashSizeKb(state.chipModel, state.firmwareSize)
  186. return sizeKb ? FLASH_LAYOUTS[sizeKb] : null
  187. }
  188. function getFirmwareValidation(byteLength = state.firmwareSize, chipModel = state.chipModel) {
  189. const chipSizeKb = inferFlashSizeKb(chipModel)
  190. const firmwareSizeKb = inferFlashSizeKbFromBytes(byteLength)
  191. const sizeKb = chipSizeKb || firmwareSizeKb
  192. const layout = sizeKb ? FLASH_LAYOUTS[sizeKb] : null
  193. const normalizedChipModel = normalizeModel(chipModel)
  194. if (!byteLength) {
  195. return {
  196. isReady: false,
  197. text: chipSizeKb
  198. ? `需要 ${formatBytes(FLASH_LAYOUTS[chipSizeKb].capacity)} .bin`
  199. : `请选择 ${FLASH_SIZE_TEXT} .bin`
  200. }
  201. }
  202. if (!layout) {
  203. return {
  204. isReady: false,
  205. text: `文件 ${formatBytes(byteLength)},应为 ${FLASH_SIZE_TEXT}`
  206. }
  207. }
  208. if (byteLength !== layout.capacity) {
  209. return {
  210. isReady: false,
  211. text: `文件 ${formatBytes(byteLength)},应为 ${formatBytes(layout.capacity)}`
  212. }
  213. }
  214. return {
  215. isReady: true,
  216. text: chipSizeKb
  217. ? `匹配 ${formatBytes(layout.capacity)}`
  218. : `${normalizedChipModel ? `${normalizedChipModel} 未识别,` : '未读到芯片型号,'}按 ${formatBytes(layout.capacity)} 尝试`
  219. }
  220. }
  221. function setChipModel(chipModel) {
  222. const nextChipModel = normalizeModel(chipModel) || '--'
  223. const validation = getFirmwareValidation(state.firmwareSize, nextChipModel)
  224. setState({
  225. chipModel: nextChipModel,
  226. bootloaderDetailText: validation.text,
  227. firmwareValidText: validation.text,
  228. isFirmwareReady: validation.isReady
  229. })
  230. }
  231. function buildFrame(payload) {
  232. return new Uint8Array(appendCrc16Ccitt(HEAD.concat(payload), BOOTLOADER_CRC_OPTIONS))
  233. }
  234. function buildHandshakeFrame() {
  235. return buildFrame([0x39, 0x42, 0x4C])
  236. }
  237. function buildUnlockFrame() {
  238. return buildFrame([0x08, 0x4E, 0x00])
  239. }
  240. function buildProgramFrame(address, dataBytes) {
  241. const payload = [
  242. 0x44,
  243. address & 0xFF,
  244. (address >> 8) & 0xFF
  245. ]
  246. const data = Array.prototype.slice.call(dataBytes || []).slice(0, PROGRAM_CHUNK_SIZE)
  247. while (data.length < PROGRAM_CHUNK_SIZE) {
  248. data.push(0x00)
  249. }
  250. return buildFrame(payload.concat(data))
  251. }
  252. function buildFlashCheckFrame() {
  253. return buildFrame([0x19, 0x43, 0x43])
  254. }
  255. function buildPageEraseFrame(enabled) {
  256. return buildFrame([0x08, 0x50, enabled ? 0x45 : 0x44])
  257. }
  258. function buildExitFrame() {
  259. return buildFrame([0x08, 0x42, 0x42])
  260. }
  261. function parseAsciiField(bytes, offset, length) {
  262. const chars = []
  263. for (let index = 0; index < length; index += 1) {
  264. const byte = bytes[offset + index] & 0xFF
  265. if (byte === 0x00 || byte === 0xFF) continue
  266. if (byte >= 0x20 && byte <= 0x7E) {
  267. chars.push(String.fromCharCode(byte))
  268. }
  269. }
  270. return chars.join('').trim() || '--'
  271. }
  272. function getHandshakeDetail(response) {
  273. if (!response) return '--'
  274. return `${response.versionText || '--'} / ${response.chipIdText || '--'}`
  275. }
  276. function alignBootloaderBuffer(buffer) {
  277. let headIndex = -1
  278. for (let index = 0; index < buffer.length - 1; index += 1) {
  279. if (buffer[index] === HEAD[0] && buffer[index + 1] === HEAD[1]) {
  280. headIndex = index
  281. break
  282. }
  283. }
  284. if (headIndex > 0) {
  285. buffer.splice(0, headIndex)
  286. } else if (headIndex < 0 && buffer.length > 1) {
  287. buffer.splice(0, buffer.length - 1)
  288. }
  289. }
  290. function parseResponse(bytes, kind) {
  291. if (!hasValidCrc16Ccitt(bytes, BOOTLOADER_CRC_OPTIONS)) {
  292. throw new Error('Bootloader 返回帧 CRC 校验失败')
  293. }
  294. if (kind === 'handshake') {
  295. if (bytes.length !== 15 || bytes[2] !== 0x39 || bytes[3] !== 0x42 || bytes[4] !== 0x4C) {
  296. throw new Error('握手反馈帧不匹配')
  297. }
  298. const versionText = parseAsciiField(bytes, 5, 4)
  299. const chipIdText = parseAsciiField(bytes, 9, 4)
  300. return {
  301. chipId: chipIdText,
  302. chipIdText,
  303. version: versionText,
  304. versionText
  305. }
  306. }
  307. if (kind === 'unlock') {
  308. if (bytes.length !== 8 || bytes[2] !== 0x08 || bytes[3] !== 0x4E || bytes[4] !== 0x00) {
  309. throw new Error('解锁反馈帧不匹配')
  310. }
  311. return {
  312. ack: bytes[5]
  313. }
  314. }
  315. if (kind === 'program') {
  316. if (bytes.length !== 8 || bytes[2] !== 0x44) {
  317. throw new Error('编程反馈帧不匹配')
  318. }
  319. return {
  320. ack: bytes[5],
  321. address: (bytes[3] & 0xFF) | ((bytes[4] & 0xFF) << 8)
  322. }
  323. }
  324. if (kind === 'flashCheck') {
  325. if (bytes.length !== 9 || bytes[2] !== 0x19 || bytes[3] !== 0x43 || bytes[4] !== 0x43) {
  326. throw new Error('全 Flash 校验反馈帧不匹配')
  327. }
  328. const flashCrc = ((bytes[5] & 0xFF) << 8) | (bytes[6] & 0xFF)
  329. return {
  330. flashCrc,
  331. flashCrcText: formatCrc(flashCrc)
  332. }
  333. }
  334. if (kind === 'pageErase') {
  335. if (bytes.length !== 8 || bytes[2] !== 0x08 || bytes[3] !== 0x50) {
  336. throw new Error('页擦除反馈帧不匹配')
  337. }
  338. return {
  339. ack: bytes[5],
  340. enabled: bytes[4] === 0x45
  341. }
  342. }
  343. return {}
  344. }
  345. function assertAck(response, label) {
  346. if (!response || response.ack === ACK) return
  347. if (response.ack === NAK) throw new Error(`${label}失败:设备返回 NAK`)
  348. throw new Error(`${label}失败:未知 ACK 0x${toHex(response.ack)}`)
  349. }
  350. function getExpectedLength(kind) {
  351. if (kind === 'handshake') return 15
  352. if (kind === 'flashCheck') return 9
  353. return 8
  354. }
  355. function waitForResponse(kind, timeout, options = {}) {
  356. const expectedLength = getExpectedLength(kind)
  357. const buffer = []
  358. return new Promise((resolve, reject) => {
  359. let settled = false
  360. let timer = null
  361. let unsubscribe = () => {}
  362. const waiter = {
  363. abort: (error) => {
  364. cleanup()
  365. reject(error)
  366. }
  367. }
  368. abortActiveResponseWaiter('新的 BootLoader 响应等待已开始')
  369. activeResponseWaiter = waiter
  370. unsubscribe = transport.subscribeRawResponse((bytes) => {
  371. buffer.push.apply(buffer, bytes)
  372. alignBootloaderBuffer(buffer)
  373. if (buffer.length < expectedLength) return
  374. const frame = buffer.slice(0, expectedLength)
  375. try {
  376. const response = parseResponse(frame, kind)
  377. cleanup()
  378. resolve(response)
  379. } catch (error) {
  380. if (options.ignoreInvalid) {
  381. buffer.shift()
  382. return
  383. }
  384. cleanup()
  385. reject(error)
  386. }
  387. })
  388. timer = setTimeout(() => {
  389. cleanup()
  390. reject(new Error(`${kind} 响应超时`))
  391. }, timeout || RESPONSE_TIMEOUT_MS)
  392. function cleanup() {
  393. if (settled) return
  394. settled = true
  395. clearTimeout(timer)
  396. if (activeResponseWaiter === waiter) {
  397. activeResponseWaiter = null
  398. }
  399. unsubscribe()
  400. }
  401. })
  402. }
  403. async function sendBootloaderFrame(frame, label, kind, timeout) {
  404. const responsePromise = kind ? waitForResponse(kind, timeout) : null
  405. const sent = await transport.sendRawFrameExact(frame, label)
  406. if (!sent) {
  407. if (responsePromise) {
  408. responsePromise.catch(() => {})
  409. abortActiveResponseWaiter(`${label}发送失败`)
  410. }
  411. throw new Error(`${label}发送失败`)
  412. }
  413. return responsePromise ? responsePromise : true
  414. }
  415. async function sendSoftReset() {
  416. return softReset({
  417. kind: 'bootloader-soft-reset'
  418. })
  419. }
  420. async function sendHandshakeKeepAlive() {
  421. if (state.isBootloaderBusy) return false
  422. const frame = buildHandshakeFrame()
  423. let finished = false
  424. setState({
  425. bootloaderDetailText: `0/${HANDSHAKE_ATTEMPTS}`,
  426. bootloaderProgress: 0,
  427. bootloaderStatusText: '握手中',
  428. isBootloaderBusy: true
  429. })
  430. const responsePromise = waitForResponse('handshake', HANDSHAKE_TIMEOUT_MS, {
  431. ignoreInvalid: true
  432. }).then((response) => {
  433. finished = true
  434. return response
  435. }).catch((error) => {
  436. finished = true
  437. throw error
  438. })
  439. responsePromise.catch(() => {})
  440. try {
  441. for (let attempt = 0; attempt < HANDSHAKE_ATTEMPTS && !finished; attempt += 1) {
  442. const sent = await transport.sendRawFrameExact(frame, 'Bootloader握手')
  443. if (!sent) throw new Error('握手帧发送失败')
  444. setState({
  445. bootloaderDetailText: `${attempt + 1}/${HANDSHAKE_ATTEMPTS}`,
  446. bootloaderProgress: Math.round((attempt + 1) / HANDSHAKE_ATTEMPTS * 100)
  447. })
  448. if (attempt < HANDSHAKE_ATTEMPTS - 1) {
  449. await delay(HANDSHAKE_INTERVAL_MS)
  450. }
  451. }
  452. const response = await responsePromise
  453. setState({
  454. bootloaderChipId: response.chipIdText,
  455. bootloaderDetailText: getHandshakeDetail(response),
  456. bootloaderProgress: 100,
  457. bootloaderStatusText: '握手成功',
  458. bootloaderVersion: response.versionText,
  459. isBootloaderBusy: false
  460. })
  461. return true
  462. } catch (error) {
  463. abortActiveResponseWaiter('握手已停止')
  464. const message = error && error.message ? error.message : '握手失败'
  465. transport.showCommandAlert('BootLoader握手', message)
  466. setState({
  467. bootloaderDetailText: message,
  468. bootloaderStatusText: '握手失败',
  469. isBootloaderBusy: false
  470. })
  471. return false
  472. }
  473. }
  474. async function handshakeUntilReady() {
  475. const frame = buildHandshakeFrame()
  476. setState({
  477. bootloaderDetailText: '软复位',
  478. bootloaderProgress: 0,
  479. bootloaderStatusText: '握手中'
  480. })
  481. const resetResponse = await sendSoftReset()
  482. setState({
  483. bootloaderDetailText: resetResponse ? '等待握手' : '软复位未响应,继续握手'
  484. })
  485. let lastError = null
  486. let finished = false
  487. const responsePromise = waitForResponse('handshake', HANDSHAKE_TIMEOUT_MS, {
  488. ignoreInvalid: true
  489. }).then((response) => {
  490. finished = true
  491. return response
  492. }).catch((error) => {
  493. finished = true
  494. throw error
  495. })
  496. for (let attempt = 0; attempt < HANDSHAKE_ATTEMPTS && !finished; attempt += 1) {
  497. try {
  498. await transport.sendRawFrameExact(frame, 'Bootloader握手')
  499. } catch (error) {
  500. lastError = error
  501. }
  502. if (!finished && attempt < HANDSHAKE_ATTEMPTS - 1) {
  503. await delay(HANDSHAKE_INTERVAL_MS)
  504. }
  505. }
  506. try {
  507. const response = await responsePromise
  508. setState({
  509. bootloaderChipId: response.chipIdText,
  510. bootloaderDetailText: getHandshakeDetail(response),
  511. bootloaderVersion: response.versionText,
  512. bootloaderStatusText: '握手成功'
  513. })
  514. return response
  515. } catch (error) {
  516. throw new Error(lastError ? `Bootloader握手失败:${lastError.message}` : `Bootloader握手失败:${error.message}`)
  517. }
  518. }
  519. async function chooseFirmwareFile(source = 'message') {
  520. if (state.isBootloaderBusy) return false
  521. try {
  522. const file = await loadSelectedFile(source, {
  523. extensionMessage: '请选择 .bin 固件文件',
  524. extensions: ['bin'],
  525. fallbackName: 'firmware.bin'
  526. })
  527. firmwareBytes = file.bytes
  528. const firmwareCrcText = formatCrc(crc16Ccitt(
  529. Array.prototype.slice.call(firmwareBytes),
  530. BOOTLOADER_CRC_OPTIONS
  531. ))
  532. const firmwareSizeText = formatBytes(firmwareBytes.length)
  533. const validation = getFirmwareValidation(firmwareBytes.length)
  534. setState({
  535. bootloaderDetailText: validation.text,
  536. bootloaderProgress: 0,
  537. bootloaderStatusText: validation.isReady ? '固件已加载' : '固件不匹配',
  538. deviceProgramCrcText: '--',
  539. firmwareChecksumText: firmwareCrcText,
  540. firmwareName: file.name || 'firmware.bin',
  541. firmwareSize: firmwareBytes.length,
  542. firmwareSizeText,
  543. firmwareValidText: validation.text,
  544. isFirmwareReady: validation.isReady
  545. })
  546. return true
  547. } catch (error) {
  548. const message = error && (error.errMsg || error.message)
  549. ? (error.errMsg || error.message)
  550. : '读取固件失败'
  551. if (!isCancelError(error)) {
  552. transport.showCommandAlert('固件文件', message)
  553. }
  554. return false
  555. }
  556. }
  557. async function startUpgrade() {
  558. if (state.isBootloaderBusy) return false
  559. if (!firmwareBytes || !state.isFirmwareReady) {
  560. transport.showCommandAlert('固件不匹配', state.firmwareValidText || `请先选择 ${FLASH_SIZE_TEXT} .bin 文件`)
  561. return false
  562. }
  563. const layout = getFlashLayout()
  564. if (!layout) {
  565. transport.showCommandAlert('固件大小', `请选择 ${FLASH_SIZE_TEXT} .bin 文件`)
  566. return false
  567. }
  568. setState({
  569. bootloaderDetailText: '',
  570. bootloaderProgress: 0,
  571. bootloaderStatusText: '握手中',
  572. isBootloaderBusy: true
  573. })
  574. try {
  575. await handshakeUntilReady()
  576. setState({
  577. bootloaderDetailText: '编程解锁',
  578. bootloaderStatusText: '升级中'
  579. })
  580. assertAck(await sendBootloaderFrame(buildUnlockFrame(), 'Bootloader解锁', 'unlock'), '编程解锁')
  581. setState({
  582. bootloaderDetailText: '开启页擦除',
  583. bootloaderStatusText: '升级中'
  584. })
  585. assertAck(await sendBootloaderFrame(buildPageEraseFrame(true), '页擦除使能', 'pageErase'), '页擦除使能')
  586. const totalBytes = layout.endAddress - layout.startAddress
  587. let programmedBytes = 0
  588. for (let address = layout.startAddress; address < layout.endAddress; address += PROGRAM_CHUNK_SIZE) {
  589. const chunk = firmwareBytes.slice(address, address + PROGRAM_CHUNK_SIZE)
  590. const response = await sendBootloaderFrame(
  591. buildProgramFrame(address, chunk),
  592. `编程 0x${toHex(address, 4)}`,
  593. 'program',
  594. PROGRAM_RESPONSE_TIMEOUT_MS
  595. )
  596. assertAck(response, `编程 0x${toHex(address, 4)}`)
  597. if (response.address !== address) {
  598. throw new Error(`编程地址反馈不匹配:0x${toHex(response.address, 4)}`)
  599. }
  600. programmedBytes = Math.min(totalBytes, programmedBytes + PROGRAM_CHUNK_SIZE)
  601. const progress = Math.min(99, Math.round(programmedBytes / totalBytes * 100))
  602. setState({
  603. bootloaderDetailText: `0x${toHex(address, 4)}`,
  604. bootloaderProgress: progress,
  605. bootloaderStatusText: `升级中 ${progress}%`
  606. })
  607. }
  608. const checkResponse = await sendBootloaderFrame(buildFlashCheckFrame(), '全Flash校验', 'flashCheck')
  609. await sendBootloaderFrame(buildExitFrame(), '退出Bootloader')
  610. setState({
  611. bootloaderDetailText: '校验通过',
  612. bootloaderProgress: 100,
  613. bootloaderStatusText: '升级完成',
  614. deviceProgramCrcText: checkResponse.flashCrcText,
  615. isBootloaderBusy: false
  616. })
  617. return true
  618. } catch (error) {
  619. const message = error && error.message ? error.message : '升级失败'
  620. transport.showCommandAlert('Bootloader升级', message)
  621. setState({
  622. bootloaderDetailText: message,
  623. bootloaderStatusText: '升级失败',
  624. isBootloaderBusy: false
  625. })
  626. return false
  627. }
  628. }
  629. async function readProgramChecksum() {
  630. if (state.isBootloaderBusy) return false
  631. setState({
  632. bootloaderDetailText: '',
  633. bootloaderStatusText: '读取中'
  634. })
  635. try {
  636. const response = await sendBootloaderFrame(buildFlashCheckFrame(), '读取程序校验码', 'flashCheck')
  637. setState({
  638. bootloaderDetailText: '程序校验码已读取',
  639. bootloaderStatusText: '读取完成',
  640. deviceProgramCrcText: response.flashCrcText
  641. })
  642. return true
  643. } catch (error) {
  644. const message = error && error.message ? error.message : '读取程序校验码失败'
  645. transport.showCommandAlert('程序校验码', message)
  646. setState({
  647. bootloaderDetailText: message,
  648. bootloaderStatusText: '读取失败'
  649. })
  650. return false
  651. }
  652. }
  653. async function exitBootloader() {
  654. if (state.isBootloaderBusy) return false
  655. try {
  656. const sent = await sendBootloaderFrame(buildExitFrame(), '退出BootLoader')
  657. if (!sent) throw new Error('退出命令发送失败')
  658. setState({
  659. bootloaderDetailText: '',
  660. bootloaderStatusText: '已退出 BootLoader'
  661. })
  662. return true
  663. } catch (error) {
  664. const message = error && error.message ? error.message : '退出 BootLoader 失败'
  665. transport.showCommandAlert('退出 BootLoader', message)
  666. setState({
  667. bootloaderDetailText: message,
  668. bootloaderStatusText: '退出失败'
  669. })
  670. return false
  671. }
  672. }
  673. module.exports = {
  674. chooseFirmwareFile,
  675. getState,
  676. init,
  677. readProgramChecksum,
  678. sendHandshakeKeepAlive,
  679. setChipModel,
  680. exitBootloader,
  681. startUpgrade,
  682. subscribe
  683. }