service.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431
  1. const transport = require('../../transport/ble-core.js')
  2. const {
  3. PROGRAM_CHUNK_SIZE,
  4. assertBootloaderAck,
  5. buildExitFrame,
  6. buildFlashCheckFrame,
  7. buildHandshakeFrame,
  8. buildPageEraseFrame,
  9. buildProgramFrame,
  10. buildUnlockFrame,
  11. calculateBootloaderCrc,
  12. formatBootloaderCrc,
  13. toHex
  14. } = require('../../protocols/bootloader/index.js')
  15. const storageAccessProtocol = require('../../protocols/storage-access/index.js')
  16. const {
  17. delay
  18. } = require('../../utils/base-utils.js')
  19. const {
  20. isCancelError,
  21. loadSelectedFile
  22. } = require('../../repositories/file.js')
  23. const {
  24. formatBytes
  25. } = require('../../utils/binary-utils.js')
  26. const firmware = require('./firmware.js')
  27. const bootloaderTransport = require('./transport.js')
  28. const HANDSHAKE_TIMEOUT_MS = 500
  29. const HANDSHAKE_INTERVAL_MS = 50
  30. const HANDSHAKE_ATTEMPTS = Math.ceil(HANDSHAKE_TIMEOUT_MS / HANDSHAKE_INTERVAL_MS)
  31. const PROGRAM_RESPONSE_TIMEOUT_MS = 6000
  32. const state = {
  33. bootloaderChipId: '--',
  34. bootloaderDetailText: '',
  35. bootloaderProgress: 0,
  36. bootloaderStatusText: '',
  37. bootloaderVersion: '--',
  38. chipModel: '--',
  39. deviceProgramCrcText: '--',
  40. firmwareChecksumText: '--',
  41. firmwareName: '',
  42. firmwareSize: 0,
  43. firmwareSizeText: '--',
  44. firmwareValidText: '未选择',
  45. isBootloaderBusy: false,
  46. isFirmwareReady: false
  47. }
  48. let firmwareBytes = null
  49. let initialized = false
  50. let unsubscribeTransport = null
  51. const subscribers = []
  52. function getState() {
  53. return {
  54. ...state
  55. }
  56. }
  57. function setState(changedData) {
  58. Object.assign(state, changedData)
  59. subscribers.slice().forEach((subscriber) => {
  60. subscriber(getState())
  61. })
  62. }
  63. function subscribe(subscriber) {
  64. if (typeof subscriber !== 'function') return () => {}
  65. subscribers.push(subscriber)
  66. subscriber(getState())
  67. return () => {
  68. const index = subscribers.indexOf(subscriber)
  69. if (index >= 0) subscribers.splice(index, 1)
  70. }
  71. }
  72. function init() {
  73. transport.init()
  74. if (initialized) return
  75. unsubscribeTransport = transport.subscribe((transportState) => {
  76. if (!transportState.connectedDevice) {
  77. bootloaderTransport.abortActiveResponseWaiter('蓝牙已断开')
  78. }
  79. if (!transportState.connectedDevice && state.isBootloaderBusy) {
  80. transport.showCommandAlert('BootLoader', '蓝牙已断开,升级已停止')
  81. setState({
  82. bootloaderDetailText: '蓝牙已断开,升级已停止',
  83. bootloaderStatusText: '升级失败',
  84. isBootloaderBusy: false
  85. })
  86. }
  87. })
  88. initialized = true
  89. }
  90. function getFlashLayout() {
  91. return firmware.getFlashLayout(state.chipModel, state.firmwareSize)
  92. }
  93. function setChipModel(chipModel) {
  94. const nextChipModel = firmware.normalizeModel(chipModel) || '--'
  95. const validation = firmware.getFirmwareValidation(state.firmwareSize, nextChipModel)
  96. setState({
  97. chipModel: nextChipModel,
  98. bootloaderDetailText: validation.text,
  99. firmwareValidText: validation.text,
  100. isFirmwareReady: validation.isReady
  101. })
  102. }
  103. function getHandshakeDetail(response) {
  104. if (!response) return '--'
  105. return `${response.versionText || '--'} / ${response.chipIdText || '--'}`
  106. }
  107. async function resetChipForUpgrade() {
  108. const resetFrame = storageAccessProtocol.buildControlFrame(storageAccessProtocol.CONTROL_OP.RESET)
  109. const sent = await bootloaderTransport.sendRawFrame(resetFrame, '存储访问复位')
  110. if (!sent) throw new Error('存储访问复位帧发送失败')
  111. }
  112. async function sendHandshakeKeepAlive() {
  113. if (state.isBootloaderBusy) return false
  114. const frame = buildHandshakeFrame()
  115. let finished = false
  116. setState({
  117. bootloaderDetailText: `0/${HANDSHAKE_ATTEMPTS}`,
  118. bootloaderProgress: 0,
  119. bootloaderStatusText: '握手中',
  120. isBootloaderBusy: true
  121. })
  122. const responsePromise = bootloaderTransport.waitForResponse('handshake', HANDSHAKE_TIMEOUT_MS, {
  123. ignoreInvalid: true
  124. }).then((response) => {
  125. finished = true
  126. return response
  127. }).catch((error) => {
  128. finished = true
  129. throw error
  130. })
  131. responsePromise.catch(() => {})
  132. try {
  133. for (let attempt = 0; attempt < HANDSHAKE_ATTEMPTS && !finished; attempt += 1) {
  134. const sent = await bootloaderTransport.sendRawFrame(frame, 'Bootloader握手')
  135. if (!sent) throw new Error('握手帧发送失败')
  136. setState({
  137. bootloaderDetailText: `${attempt + 1}/${HANDSHAKE_ATTEMPTS}`,
  138. bootloaderProgress: Math.round((attempt + 1) / HANDSHAKE_ATTEMPTS * 100)
  139. })
  140. if (attempt < HANDSHAKE_ATTEMPTS - 1) {
  141. await delay(HANDSHAKE_INTERVAL_MS)
  142. }
  143. }
  144. const response = await responsePromise
  145. setState({
  146. bootloaderChipId: response.chipIdText,
  147. bootloaderDetailText: getHandshakeDetail(response),
  148. bootloaderProgress: 100,
  149. bootloaderStatusText: '握手成功',
  150. bootloaderVersion: response.versionText,
  151. isBootloaderBusy: false
  152. })
  153. return true
  154. } catch (error) {
  155. bootloaderTransport.abortActiveResponseWaiter('握手已停止')
  156. const message = error && error.message ? error.message : '握手失败'
  157. transport.showCommandAlert('BootLoader握手', message)
  158. setState({
  159. bootloaderDetailText: message,
  160. bootloaderStatusText: '握手失败',
  161. isBootloaderBusy: false
  162. })
  163. return false
  164. }
  165. }
  166. async function handshakeUntilReady() {
  167. const frame = buildHandshakeFrame()
  168. setState({
  169. bootloaderDetailText: '等待握手',
  170. bootloaderProgress: 0,
  171. bootloaderStatusText: '握手中'
  172. })
  173. let lastError = null
  174. let finished = false
  175. const responsePromise = bootloaderTransport.waitForResponse('handshake', HANDSHAKE_TIMEOUT_MS, {
  176. ignoreInvalid: true
  177. }).then((response) => {
  178. finished = true
  179. return response
  180. }).catch((error) => {
  181. finished = true
  182. throw error
  183. })
  184. for (let attempt = 0; attempt < HANDSHAKE_ATTEMPTS && !finished; attempt += 1) {
  185. try {
  186. await bootloaderTransport.sendRawFrame(frame, 'Bootloader握手')
  187. } catch (error) {
  188. lastError = error
  189. }
  190. if (!finished && attempt < HANDSHAKE_ATTEMPTS - 1) {
  191. await delay(HANDSHAKE_INTERVAL_MS)
  192. }
  193. }
  194. try {
  195. const response = await responsePromise
  196. setState({
  197. bootloaderChipId: response.chipIdText,
  198. bootloaderDetailText: getHandshakeDetail(response),
  199. bootloaderVersion: response.versionText,
  200. bootloaderStatusText: '握手成功'
  201. })
  202. return response
  203. } catch (error) {
  204. throw new Error(lastError ? `Bootloader握手失败:${lastError.message}` : `Bootloader握手失败:${error.message}`)
  205. }
  206. }
  207. async function chooseFirmwareFile(source = 'auto') {
  208. if (state.isBootloaderBusy) return false
  209. try {
  210. const file = await loadSelectedFile(source, {
  211. extensionMessage: '请选择 .bin 固件文件',
  212. extensions: ['bin'],
  213. fallbackName: 'firmware.bin'
  214. })
  215. firmwareBytes = file.bytes
  216. const firmwareCrcText = formatBootloaderCrc(calculateBootloaderCrc(firmwareBytes))
  217. const firmwareSizeText = formatBytes(firmwareBytes.length)
  218. const validation = firmware.getFirmwareValidation(firmwareBytes.length, state.chipModel)
  219. setState({
  220. bootloaderDetailText: validation.text,
  221. bootloaderProgress: 0,
  222. bootloaderStatusText: validation.isReady ? '固件已加载' : '固件不匹配',
  223. deviceProgramCrcText: '--',
  224. firmwareChecksumText: firmwareCrcText,
  225. firmwareName: file.name || 'firmware.bin',
  226. firmwareSize: firmwareBytes.length,
  227. firmwareSizeText,
  228. firmwareValidText: validation.text,
  229. isFirmwareReady: validation.isReady
  230. })
  231. return true
  232. } catch (error) {
  233. const message = error && (error.errMsg || error.message)
  234. ? (error.errMsg || error.message)
  235. : '读取固件失败'
  236. if (!isCancelError(error)) {
  237. transport.showCommandAlert('固件文件', message)
  238. }
  239. return false
  240. }
  241. }
  242. async function startUpgrade() {
  243. if (state.isBootloaderBusy) return false
  244. if (!firmwareBytes || !state.isFirmwareReady) {
  245. transport.showCommandAlert('固件不匹配', state.firmwareValidText || `请先选择 ${firmware.FLASH_SIZE_TEXT} .bin 文件`)
  246. return false
  247. }
  248. const layout = getFlashLayout()
  249. if (!layout) {
  250. transport.showCommandAlert('固件大小', `请选择 ${firmware.FLASH_SIZE_TEXT} .bin 文件`)
  251. return false
  252. }
  253. setState({
  254. bootloaderDetailText: '',
  255. bootloaderProgress: 0,
  256. bootloaderStatusText: '复位中',
  257. isBootloaderBusy: true
  258. })
  259. try {
  260. await resetChipForUpgrade()
  261. await handshakeUntilReady()
  262. setState({
  263. bootloaderDetailText: '编程解锁',
  264. bootloaderStatusText: '升级中'
  265. })
  266. assertBootloaderAck(await bootloaderTransport.sendFrame(buildUnlockFrame(), 'Bootloader解锁', 'unlock'), '编程解锁')
  267. setState({
  268. bootloaderDetailText: '开启页擦除',
  269. bootloaderStatusText: '升级中'
  270. })
  271. assertBootloaderAck(await bootloaderTransport.sendFrame(buildPageEraseFrame(true), '页擦除使能', 'pageErase'), '页擦除使能')
  272. const totalBytes = layout.endAddress - layout.startAddress
  273. let programmedBytes = 0
  274. for (let address = layout.startAddress; address < layout.endAddress; address += PROGRAM_CHUNK_SIZE) {
  275. const chunk = firmwareBytes.slice(address, address + PROGRAM_CHUNK_SIZE)
  276. const response = await bootloaderTransport.sendFrame(
  277. buildProgramFrame(address, chunk),
  278. `编程 0x${toHex(address, 4)}`,
  279. 'program',
  280. PROGRAM_RESPONSE_TIMEOUT_MS
  281. )
  282. assertBootloaderAck(response, `编程 0x${toHex(address, 4)}`)
  283. if (response.address !== address) {
  284. throw new Error(`编程地址反馈不匹配:0x${toHex(response.address, 4)}`)
  285. }
  286. programmedBytes = Math.min(totalBytes, programmedBytes + PROGRAM_CHUNK_SIZE)
  287. const progress = Math.min(99, Math.round(programmedBytes / totalBytes * 100))
  288. setState({
  289. bootloaderDetailText: `0x${toHex(address, 4)}`,
  290. bootloaderProgress: progress,
  291. bootloaderStatusText: `升级中 ${progress}%`
  292. })
  293. }
  294. const checkResponse = await bootloaderTransport.sendFrame(buildFlashCheckFrame(), '全Flash校验', 'flashCheck')
  295. await bootloaderTransport.sendFrame(buildExitFrame(), '退出Bootloader')
  296. setState({
  297. bootloaderDetailText: '校验通过',
  298. bootloaderProgress: 100,
  299. bootloaderStatusText: '升级完成',
  300. deviceProgramCrcText: checkResponse.flashCrcText,
  301. isBootloaderBusy: false
  302. })
  303. return true
  304. } catch (error) {
  305. const message = error && error.message ? error.message : '升级失败'
  306. transport.showCommandAlert('Bootloader升级', message)
  307. setState({
  308. bootloaderDetailText: message,
  309. bootloaderStatusText: '升级失败',
  310. isBootloaderBusy: false
  311. })
  312. return false
  313. }
  314. }
  315. async function readProgramChecksum() {
  316. if (state.isBootloaderBusy) return false
  317. setState({
  318. bootloaderDetailText: '',
  319. bootloaderStatusText: '读取中'
  320. })
  321. try {
  322. const response = await bootloaderTransport.sendFrame(buildFlashCheckFrame(), '读取程序校验码', 'flashCheck')
  323. setState({
  324. bootloaderDetailText: '程序校验码已读取',
  325. bootloaderStatusText: '读取完成',
  326. deviceProgramCrcText: response.flashCrcText
  327. })
  328. return true
  329. } catch (error) {
  330. const message = error && error.message ? error.message : '读取程序校验码失败'
  331. transport.showCommandAlert('程序校验码', message)
  332. setState({
  333. bootloaderDetailText: message,
  334. bootloaderStatusText: '读取失败'
  335. })
  336. return false
  337. }
  338. }
  339. async function exitBootloader() {
  340. if (state.isBootloaderBusy) return false
  341. try {
  342. const sent = await bootloaderTransport.sendFrame(buildExitFrame(), '退出BootLoader')
  343. if (!sent) throw new Error('退出命令发送失败')
  344. setState({
  345. bootloaderDetailText: '',
  346. bootloaderStatusText: '已退出 BootLoader'
  347. })
  348. return true
  349. } catch (error) {
  350. const message = error && error.message ? error.message : '退出 BootLoader 失败'
  351. transport.showCommandAlert('退出 BootLoader', message)
  352. setState({
  353. bootloaderDetailText: message,
  354. bootloaderStatusText: '退出失败'
  355. })
  356. return false
  357. }
  358. }
  359. module.exports = {
  360. chooseFirmwareFile,
  361. getState,
  362. init,
  363. readProgramChecksum,
  364. sendHandshakeKeepAlive,
  365. setChipModel,
  366. exitBootloader,
  367. startUpgrade,
  368. subscribe
  369. }