1
0

service.js 12 KB

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