| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422 |
- const transport = require('../../transport/ble-core.js')
- const {
- PROGRAM_CHUNK_SIZE,
- assertBootloaderAck,
- buildExitFrame,
- buildFlashCheckFrame,
- buildHandshakeFrame,
- buildPageEraseFrame,
- buildProgramFrame,
- buildUnlockFrame,
- calculateBootloaderCrc,
- formatBootloaderCrc,
- toHex
- } = require('../../protocols/bootloader/index.js')
- const {
- delay
- } = require('../../utils/base-utils.js')
- const {
- isCancelError,
- loadSelectedFile
- } = require('../../repositories/file.js')
- const {
- formatBytes
- } = require('../../utils/binary-utils.js')
- const firmware = require('./firmware.js')
- const bootloaderTransport = require('./transport.js')
- const HANDSHAKE_INTERVAL_MS = 200
- const HANDSHAKE_ATTEMPTS = 10
- const HANDSHAKE_TIMEOUT_MS = HANDSHAKE_INTERVAL_MS * HANDSHAKE_ATTEMPTS
- const PROGRAM_RESPONSE_TIMEOUT_MS = 6000
- const state = {
- bootloaderChipId: '--',
- bootloaderDetailText: '',
- bootloaderProgress: 0,
- bootloaderStatusText: '',
- bootloaderVersion: '--',
- chipModel: '--',
- deviceProgramCrcText: '--',
- firmwareChecksumText: '--',
- firmwareName: '',
- firmwareSize: 0,
- firmwareSizeText: '--',
- firmwareValidText: '未选择',
- isBootloaderBusy: false,
- isFirmwareReady: false
- }
- let firmwareBytes = null
- let initialized = false
- let unsubscribeTransport = null
- const subscribers = []
- function getState() {
- return {
- ...state
- }
- }
- function setState(changedData) {
- Object.assign(state, changedData)
- subscribers.slice().forEach((subscriber) => {
- subscriber(getState())
- })
- }
- function subscribe(subscriber) {
- if (typeof subscriber !== 'function') return () => {}
- subscribers.push(subscriber)
- subscriber(getState())
- return () => {
- const index = subscribers.indexOf(subscriber)
- if (index >= 0) subscribers.splice(index, 1)
- }
- }
- function init() {
- transport.init()
- if (initialized) return
- unsubscribeTransport = transport.subscribe((transportState) => {
- if (!transportState.connectedDevice) {
- bootloaderTransport.abortActiveResponseWaiter('蓝牙已断开')
- }
- if (!transportState.connectedDevice && state.isBootloaderBusy) {
- transport.showCommandAlert('BootLoader', '蓝牙已断开,升级已停止')
- setState({
- bootloaderDetailText: '蓝牙已断开,升级已停止',
- bootloaderStatusText: '升级失败',
- isBootloaderBusy: false
- })
- }
- })
- initialized = true
- }
- function getFlashLayout() {
- return firmware.getFlashLayout(state.chipModel, state.firmwareSize)
- }
- function setChipModel(chipModel) {
- const nextChipModel = firmware.normalizeModel(chipModel) || '--'
- const validation = firmware.getFirmwareValidation(state.firmwareSize, nextChipModel)
- setState({
- chipModel: nextChipModel,
- bootloaderDetailText: validation.text,
- firmwareValidText: validation.text,
- isFirmwareReady: validation.isReady
- })
- }
- function getHandshakeDetail(response) {
- if (!response) return '--'
- return `${response.versionText || '--'} / ${response.chipIdText || '--'}`
- }
- async function sendHandshakeKeepAlive() {
- if (state.isBootloaderBusy) return false
- const frame = buildHandshakeFrame()
- let finished = false
- setState({
- bootloaderDetailText: `0/${HANDSHAKE_ATTEMPTS}`,
- bootloaderProgress: 0,
- bootloaderStatusText: '握手中',
- isBootloaderBusy: true
- })
- const responsePromise = bootloaderTransport.waitForResponse('handshake', HANDSHAKE_TIMEOUT_MS, {
- ignoreInvalid: true
- }).then((response) => {
- finished = true
- return response
- }).catch((error) => {
- finished = true
- throw error
- })
- responsePromise.catch(() => {})
- try {
- for (let attempt = 0; attempt < HANDSHAKE_ATTEMPTS && !finished; attempt += 1) {
- const sent = await bootloaderTransport.sendRawFrame(frame, 'Bootloader握手')
- if (!sent) throw new Error('握手帧发送失败')
- setState({
- bootloaderDetailText: `${attempt + 1}/${HANDSHAKE_ATTEMPTS}`,
- bootloaderProgress: Math.round((attempt + 1) / HANDSHAKE_ATTEMPTS * 100)
- })
- if (attempt < HANDSHAKE_ATTEMPTS - 1) {
- await delay(HANDSHAKE_INTERVAL_MS)
- }
- }
- const response = await responsePromise
- setState({
- bootloaderChipId: response.chipIdText,
- bootloaderDetailText: getHandshakeDetail(response),
- bootloaderProgress: 100,
- bootloaderStatusText: '握手成功',
- bootloaderVersion: response.versionText,
- isBootloaderBusy: false
- })
- return true
- } catch (error) {
- bootloaderTransport.abortActiveResponseWaiter('握手已停止')
- const message = error && error.message ? error.message : '握手失败'
- transport.showCommandAlert('BootLoader握手', message)
- setState({
- bootloaderDetailText: message,
- bootloaderStatusText: '握手失败',
- isBootloaderBusy: false
- })
- return false
- }
- }
- async function handshakeUntilReady() {
- const frame = buildHandshakeFrame()
- setState({
- bootloaderDetailText: '等待握手',
- bootloaderProgress: 0,
- bootloaderStatusText: '握手中'
- })
- let lastError = null
- let finished = false
- const responsePromise = bootloaderTransport.waitForResponse('handshake', HANDSHAKE_TIMEOUT_MS, {
- ignoreInvalid: true
- }).then((response) => {
- finished = true
- return response
- }).catch((error) => {
- finished = true
- throw error
- })
- for (let attempt = 0; attempt < HANDSHAKE_ATTEMPTS && !finished; attempt += 1) {
- try {
- await bootloaderTransport.sendRawFrame(frame, 'Bootloader握手')
- } catch (error) {
- lastError = error
- }
- if (!finished && attempt < HANDSHAKE_ATTEMPTS - 1) {
- await delay(HANDSHAKE_INTERVAL_MS)
- }
- }
- try {
- const response = await responsePromise
- setState({
- bootloaderChipId: response.chipIdText,
- bootloaderDetailText: getHandshakeDetail(response),
- bootloaderVersion: response.versionText,
- bootloaderStatusText: '握手成功'
- })
- return response
- } catch (error) {
- throw new Error(lastError ? `Bootloader握手失败:${lastError.message}` : `Bootloader握手失败:${error.message}`)
- }
- }
- async function chooseFirmwareFile(source = 'message') {
- if (state.isBootloaderBusy) return false
- try {
- const file = await loadSelectedFile(source, {
- extensionMessage: '请选择 .bin 固件文件',
- extensions: ['bin'],
- fallbackName: 'firmware.bin'
- })
- firmwareBytes = file.bytes
- const firmwareCrcText = formatBootloaderCrc(calculateBootloaderCrc(firmwareBytes))
- const firmwareSizeText = formatBytes(firmwareBytes.length)
- const validation = firmware.getFirmwareValidation(firmwareBytes.length, state.chipModel)
- setState({
- bootloaderDetailText: validation.text,
- bootloaderProgress: 0,
- bootloaderStatusText: validation.isReady ? '固件已加载' : '固件不匹配',
- deviceProgramCrcText: '--',
- firmwareChecksumText: firmwareCrcText,
- firmwareName: file.name || 'firmware.bin',
- firmwareSize: firmwareBytes.length,
- firmwareSizeText,
- firmwareValidText: validation.text,
- isFirmwareReady: validation.isReady
- })
- return true
- } catch (error) {
- const message = error && (error.errMsg || error.message)
- ? (error.errMsg || error.message)
- : '读取固件失败'
- if (!isCancelError(error)) {
- transport.showCommandAlert('固件文件', message)
- }
- return false
- }
- }
- async function startUpgrade() {
- if (state.isBootloaderBusy) return false
- if (!firmwareBytes || !state.isFirmwareReady) {
- transport.showCommandAlert('固件不匹配', state.firmwareValidText || `请先选择 ${firmware.FLASH_SIZE_TEXT} .bin 文件`)
- return false
- }
- const layout = getFlashLayout()
- if (!layout) {
- transport.showCommandAlert('固件大小', `请选择 ${firmware.FLASH_SIZE_TEXT} .bin 文件`)
- return false
- }
- setState({
- bootloaderDetailText: '',
- bootloaderProgress: 0,
- bootloaderStatusText: '握手中',
- isBootloaderBusy: true
- })
- try {
- await handshakeUntilReady()
- setState({
- bootloaderDetailText: '编程解锁',
- bootloaderStatusText: '升级中'
- })
- assertBootloaderAck(await bootloaderTransport.sendFrame(buildUnlockFrame(), 'Bootloader解锁', 'unlock'), '编程解锁')
- setState({
- bootloaderDetailText: '开启页擦除',
- bootloaderStatusText: '升级中'
- })
- assertBootloaderAck(await bootloaderTransport.sendFrame(buildPageEraseFrame(true), '页擦除使能', 'pageErase'), '页擦除使能')
- const totalBytes = layout.endAddress - layout.startAddress
- let programmedBytes = 0
- for (let address = layout.startAddress; address < layout.endAddress; address += PROGRAM_CHUNK_SIZE) {
- const chunk = firmwareBytes.slice(address, address + PROGRAM_CHUNK_SIZE)
- const response = await bootloaderTransport.sendFrame(
- buildProgramFrame(address, chunk),
- `编程 0x${toHex(address, 4)}`,
- 'program',
- PROGRAM_RESPONSE_TIMEOUT_MS
- )
- assertBootloaderAck(response, `编程 0x${toHex(address, 4)}`)
- if (response.address !== address) {
- throw new Error(`编程地址反馈不匹配:0x${toHex(response.address, 4)}`)
- }
- programmedBytes = Math.min(totalBytes, programmedBytes + PROGRAM_CHUNK_SIZE)
- const progress = Math.min(99, Math.round(programmedBytes / totalBytes * 100))
- setState({
- bootloaderDetailText: `0x${toHex(address, 4)}`,
- bootloaderProgress: progress,
- bootloaderStatusText: `升级中 ${progress}%`
- })
- }
- const checkResponse = await bootloaderTransport.sendFrame(buildFlashCheckFrame(), '全Flash校验', 'flashCheck')
- await bootloaderTransport.sendFrame(buildExitFrame(), '退出Bootloader')
- setState({
- bootloaderDetailText: '校验通过',
- bootloaderProgress: 100,
- bootloaderStatusText: '升级完成',
- deviceProgramCrcText: checkResponse.flashCrcText,
- isBootloaderBusy: false
- })
- return true
- } catch (error) {
- const message = error && error.message ? error.message : '升级失败'
- transport.showCommandAlert('Bootloader升级', message)
- setState({
- bootloaderDetailText: message,
- bootloaderStatusText: '升级失败',
- isBootloaderBusy: false
- })
- return false
- }
- }
- async function readProgramChecksum() {
- if (state.isBootloaderBusy) return false
- setState({
- bootloaderDetailText: '',
- bootloaderStatusText: '读取中'
- })
- try {
- const response = await bootloaderTransport.sendFrame(buildFlashCheckFrame(), '读取程序校验码', 'flashCheck')
- setState({
- bootloaderDetailText: '程序校验码已读取',
- bootloaderStatusText: '读取完成',
- deviceProgramCrcText: response.flashCrcText
- })
- return true
- } catch (error) {
- const message = error && error.message ? error.message : '读取程序校验码失败'
- transport.showCommandAlert('程序校验码', message)
- setState({
- bootloaderDetailText: message,
- bootloaderStatusText: '读取失败'
- })
- return false
- }
- }
- async function exitBootloader() {
- if (state.isBootloaderBusy) return false
- try {
- const sent = await bootloaderTransport.sendFrame(buildExitFrame(), '退出BootLoader')
- if (!sent) throw new Error('退出命令发送失败')
- setState({
- bootloaderDetailText: '',
- bootloaderStatusText: '已退出 BootLoader'
- })
- return true
- } catch (error) {
- const message = error && error.message ? error.message : '退出 BootLoader 失败'
- transport.showCommandAlert('退出 BootLoader', message)
- setState({
- bootloaderDetailText: message,
- bootloaderStatusText: '退出失败'
- })
- return false
- }
- }
- module.exports = {
- chooseFirmwareFile,
- getState,
- init,
- readProgramChecksum,
- sendHandshakeKeepAlive,
- setChipModel,
- exitBootloader,
- startUpgrade,
- subscribe
- }
|