| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806 |
- const transport = require('./ble-transport')
- const {
- BYTE_ORDER_HIGH,
- crc16Ccitt,
- appendCrc16Ccitt,
- hasValidCrc16Ccitt
- } = require('./crc')
- const {
- BOOTLOADER_HEAD
- } = require('./bootloader-frame')
- const {
- softReset
- } = require('./motor-control-protocol')
- const {
- delay
- } = require('./base-utils')
- const {
- formatBytes,
- isCancelError,
- loadSelectedFile
- } = require('./file-service')
- const HEAD = BOOTLOADER_HEAD
- const BOOTLOADER_CRC_OPTIONS = {
- byteOrder: BYTE_ORDER_HIGH
- }
- const ACK = 0x06
- const NAK = 0x15
- const HANDSHAKE_INTERVAL_MS = 200
- const HANDSHAKE_ATTEMPTS = 10
- const HANDSHAKE_TIMEOUT_MS = HANDSHAKE_INTERVAL_MS * HANDSHAKE_ATTEMPTS
- const RESPONSE_TIMEOUT_MS = 3000
- const PROGRAM_RESPONSE_TIMEOUT_MS = 6000
- const PROGRAM_CHUNK_SIZE = 128
- const FILE_SIZES = {
- 16: 16 * 1024,
- 32: 32 * 1024
- }
- const FLASH_LAYOUTS = {
- 16: {
- capacity: FILE_SIZES[16],
- startAddress: 0x0400,
- endAddress: 0x4000
- },
- 32: {
- capacity: FILE_SIZES[32],
- startAddress: 0x0800,
- endAddress: 0x8000
- }
- }
- const FLASH_SIZE_TEXT = Object.keys(FILE_SIZES)
- .map((sizeKb) => formatBytes(FILE_SIZES[sizeKb]))
- .join(' 或 ')
- const CHIP_FLASH_SIZE_KB = {
- FU6572L: 32,
- FU6572N: 32,
- FU6572T: 32,
- FU6565N: 32,
- FU6565T: 32,
- FU6563N: 32,
- FU6562L: 32,
- FU6562LA: 32,
- FU6562Q: 32,
- FU6562S: 32,
- FU6562T: 32,
- FU6532N: 32,
- FU6532T: 32,
- FU6522L: 32,
- FU6522N: 32,
- FU6522T: 32,
- FU6812L2: 16,
- FU6812N2: 16,
- FU6812S2: 16,
- FU6812V: 16,
- FU6861Q2: 16,
- FU6861N2: 16,
- FU6861NF2: 16,
- FU6861L2: 16,
- FU6862L: 16,
- FU6862Q: 16,
- FU6872P: 16
- }
- const CHIP_FAMILY_FLASH_SIZE_KB = {
- 65: 32,
- 68: 16
- }
- 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
- let activeResponseWaiter = null
- const subscribers = []
- function getState() {
- return {
- ...state
- }
- }
- function setState(changedData) {
- Object.assign(state, changedData)
- subscribers.slice().forEach((subscriber) => {
- subscriber(getState())
- })
- }
- function abortActiveResponseWaiter(message) {
- if (!activeResponseWaiter) return false
- const waiter = activeResponseWaiter
- activeResponseWaiter = null
- waiter.abort(new Error(message || '蓝牙已断开'))
- return true
- }
- 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) {
- abortActiveResponseWaiter('蓝牙已断开')
- }
- if (!transportState.connectedDevice && state.isBootloaderBusy) {
- transport.showCommandAlert('BootLoader', '蓝牙已断开,升级已停止')
- setState({
- bootloaderDetailText: '蓝牙已断开,升级已停止',
- bootloaderStatusText: '升级失败',
- isBootloaderBusy: false
- })
- }
- })
- initialized = true
- }
- function toHex(value, length = 2) {
- return Number(value || 0).toString(16).toUpperCase().padStart(length, '0')
- }
- function formatCrc(value) {
- return `0x${toHex(value, 4)}`
- }
- function normalizeModel(value) {
- const text = String(value || '').trim()
- return text && text !== '--' ? text : ''
- }
- function extractChipModels(chipModel) {
- const model = normalizeModel(chipModel).toUpperCase()
- return model.match(/FU\d{4}[A-Z0-9]*/g) || []
- }
- function inferFlashSizeKb(chipModel) {
- const models = extractChipModels(chipModel)
- for (const model of models) {
- if (CHIP_FLASH_SIZE_KB[model]) return CHIP_FLASH_SIZE_KB[model]
- const family = model.slice(2, 4)
- if (CHIP_FAMILY_FLASH_SIZE_KB[family]) return CHIP_FAMILY_FLASH_SIZE_KB[family]
- }
- const text = normalizeModel(chipModel).toUpperCase()
- if (/(^|[^0-9])32\s*K(B)?([^0-9]|$)/.test(text)) return 32
- if (/(^|[^0-9])16\s*K(B)?([^0-9]|$)/.test(text)) return 16
- return null
- }
- function inferFlashSizeKbFromBytes(byteLength) {
- return Number(Object.keys(FILE_SIZES).find((sizeKb) => FILE_SIZES[sizeKb] === byteLength)) || null
- }
- function inferUpgradeFlashSizeKb(chipModel, byteLength) {
- return inferFlashSizeKb(chipModel) || inferFlashSizeKbFromBytes(byteLength)
- }
- function getFlashLayout() {
- const sizeKb = inferUpgradeFlashSizeKb(state.chipModel, state.firmwareSize)
- return sizeKb ? FLASH_LAYOUTS[sizeKb] : null
- }
- function getFirmwareValidation(byteLength = state.firmwareSize, chipModel = state.chipModel) {
- const chipSizeKb = inferFlashSizeKb(chipModel)
- const firmwareSizeKb = inferFlashSizeKbFromBytes(byteLength)
- const sizeKb = chipSizeKb || firmwareSizeKb
- const layout = sizeKb ? FLASH_LAYOUTS[sizeKb] : null
- const normalizedChipModel = normalizeModel(chipModel)
- if (!byteLength) {
- return {
- isReady: false,
- text: chipSizeKb
- ? `需要 ${formatBytes(FLASH_LAYOUTS[chipSizeKb].capacity)} .bin`
- : `请选择 ${FLASH_SIZE_TEXT} .bin`
- }
- }
- if (!layout) {
- return {
- isReady: false,
- text: `文件 ${formatBytes(byteLength)},应为 ${FLASH_SIZE_TEXT}`
- }
- }
- if (byteLength !== layout.capacity) {
- return {
- isReady: false,
- text: `文件 ${formatBytes(byteLength)},应为 ${formatBytes(layout.capacity)}`
- }
- }
- return {
- isReady: true,
- text: chipSizeKb
- ? `匹配 ${formatBytes(layout.capacity)}`
- : `${normalizedChipModel ? `${normalizedChipModel} 未识别,` : '未读到芯片型号,'}按 ${formatBytes(layout.capacity)} 尝试`
- }
- }
- function setChipModel(chipModel) {
- const nextChipModel = normalizeModel(chipModel) || '--'
- const validation = getFirmwareValidation(state.firmwareSize, nextChipModel)
- setState({
- chipModel: nextChipModel,
- bootloaderDetailText: validation.text,
- firmwareValidText: validation.text,
- isFirmwareReady: validation.isReady
- })
- }
- function buildFrame(payload) {
- return new Uint8Array(appendCrc16Ccitt(HEAD.concat(payload), BOOTLOADER_CRC_OPTIONS))
- }
- function buildHandshakeFrame() {
- return buildFrame([0x39, 0x42, 0x4C])
- }
- function buildUnlockFrame() {
- return buildFrame([0x08, 0x4E, 0x00])
- }
- function buildProgramFrame(address, dataBytes) {
- const payload = [
- 0x44,
- address & 0xFF,
- (address >> 8) & 0xFF
- ]
- const data = Array.prototype.slice.call(dataBytes || []).slice(0, PROGRAM_CHUNK_SIZE)
- while (data.length < PROGRAM_CHUNK_SIZE) {
- data.push(0x00)
- }
- return buildFrame(payload.concat(data))
- }
- function buildFlashCheckFrame() {
- return buildFrame([0x19, 0x43, 0x43])
- }
- function buildPageEraseFrame(enabled) {
- return buildFrame([0x08, 0x50, enabled ? 0x45 : 0x44])
- }
- function buildExitFrame() {
- return buildFrame([0x08, 0x42, 0x42])
- }
- function parseAsciiField(bytes, offset, length) {
- const chars = []
- for (let index = 0; index < length; index += 1) {
- const byte = bytes[offset + index] & 0xFF
- if (byte === 0x00 || byte === 0xFF) continue
- if (byte >= 0x20 && byte <= 0x7E) {
- chars.push(String.fromCharCode(byte))
- }
- }
- return chars.join('').trim() || '--'
- }
- function getHandshakeDetail(response) {
- if (!response) return '--'
- return `${response.versionText || '--'} / ${response.chipIdText || '--'}`
- }
- function alignBootloaderBuffer(buffer) {
- let headIndex = -1
- for (let index = 0; index < buffer.length - 1; index += 1) {
- if (buffer[index] === HEAD[0] && buffer[index + 1] === HEAD[1]) {
- headIndex = index
- break
- }
- }
- if (headIndex > 0) {
- buffer.splice(0, headIndex)
- } else if (headIndex < 0 && buffer.length > 1) {
- buffer.splice(0, buffer.length - 1)
- }
- }
- function parseResponse(bytes, kind) {
- if (!hasValidCrc16Ccitt(bytes, BOOTLOADER_CRC_OPTIONS)) {
- throw new Error('Bootloader 返回帧 CRC 校验失败')
- }
- if (kind === 'handshake') {
- if (bytes.length !== 15 || bytes[2] !== 0x39 || bytes[3] !== 0x42 || bytes[4] !== 0x4C) {
- throw new Error('握手反馈帧不匹配')
- }
- const versionText = parseAsciiField(bytes, 5, 4)
- const chipIdText = parseAsciiField(bytes, 9, 4)
- return {
- chipId: chipIdText,
- chipIdText,
- version: versionText,
- versionText
- }
- }
- if (kind === 'unlock') {
- if (bytes.length !== 8 || bytes[2] !== 0x08 || bytes[3] !== 0x4E || bytes[4] !== 0x00) {
- throw new Error('解锁反馈帧不匹配')
- }
- return {
- ack: bytes[5]
- }
- }
- if (kind === 'program') {
- if (bytes.length !== 8 || bytes[2] !== 0x44) {
- throw new Error('编程反馈帧不匹配')
- }
- return {
- ack: bytes[5],
- address: (bytes[3] & 0xFF) | ((bytes[4] & 0xFF) << 8)
- }
- }
- if (kind === 'flashCheck') {
- if (bytes.length !== 9 || bytes[2] !== 0x19 || bytes[3] !== 0x43 || bytes[4] !== 0x43) {
- throw new Error('全 Flash 校验反馈帧不匹配')
- }
- const flashCrc = ((bytes[5] & 0xFF) << 8) | (bytes[6] & 0xFF)
- return {
- flashCrc,
- flashCrcText: formatCrc(flashCrc)
- }
- }
- if (kind === 'pageErase') {
- if (bytes.length !== 8 || bytes[2] !== 0x08 || bytes[3] !== 0x50) {
- throw new Error('页擦除反馈帧不匹配')
- }
- return {
- ack: bytes[5],
- enabled: bytes[4] === 0x45
- }
- }
- return {}
- }
- function assertAck(response, label) {
- if (!response || response.ack === ACK) return
- if (response.ack === NAK) throw new Error(`${label}失败:设备返回 NAK`)
- throw new Error(`${label}失败:未知 ACK 0x${toHex(response.ack)}`)
- }
- function getExpectedLength(kind) {
- if (kind === 'handshake') return 15
- if (kind === 'flashCheck') return 9
- return 8
- }
- function waitForResponse(kind, timeout, options = {}) {
- const expectedLength = getExpectedLength(kind)
- const buffer = []
- return new Promise((resolve, reject) => {
- let settled = false
- let timer = null
- let unsubscribe = () => {}
- const waiter = {
- abort: (error) => {
- cleanup()
- reject(error)
- }
- }
- abortActiveResponseWaiter('新的 BootLoader 响应等待已开始')
- activeResponseWaiter = waiter
- unsubscribe = transport.subscribeRawResponse((bytes) => {
- buffer.push.apply(buffer, bytes)
- alignBootloaderBuffer(buffer)
- if (buffer.length < expectedLength) return
- const frame = buffer.slice(0, expectedLength)
- try {
- const response = parseResponse(frame, kind)
- cleanup()
- resolve(response)
- } catch (error) {
- if (options.ignoreInvalid) {
- buffer.shift()
- return
- }
- cleanup()
- reject(error)
- }
- })
- timer = setTimeout(() => {
- cleanup()
- reject(new Error(`${kind} 响应超时`))
- }, timeout || RESPONSE_TIMEOUT_MS)
- function cleanup() {
- if (settled) return
- settled = true
- clearTimeout(timer)
- if (activeResponseWaiter === waiter) {
- activeResponseWaiter = null
- }
- unsubscribe()
- }
- })
- }
- async function sendBootloaderFrame(frame, label, kind, timeout) {
- const responsePromise = kind ? waitForResponse(kind, timeout) : null
- const sent = await transport.sendRawFrameExact(frame, label)
- if (!sent) {
- if (responsePromise) {
- responsePromise.catch(() => {})
- abortActiveResponseWaiter(`${label}发送失败`)
- }
- throw new Error(`${label}发送失败`)
- }
- return responsePromise ? responsePromise : true
- }
- async function sendSoftReset() {
- return softReset({
- kind: 'bootloader-soft-reset'
- })
- }
- 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 = 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 transport.sendRawFrameExact(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) {
- 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: '握手中'
- })
- const resetResponse = await sendSoftReset()
- setState({
- bootloaderDetailText: resetResponse ? '等待握手' : '软复位未响应,继续握手'
- })
- let lastError = null
- let finished = false
- const responsePromise = 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 transport.sendRawFrameExact(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 = formatCrc(crc16Ccitt(
- Array.prototype.slice.call(firmwareBytes),
- BOOTLOADER_CRC_OPTIONS
- ))
- const firmwareSizeText = formatBytes(firmwareBytes.length)
- const validation = getFirmwareValidation(firmwareBytes.length)
- 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 || `请先选择 ${FLASH_SIZE_TEXT} .bin 文件`)
- return false
- }
- const layout = getFlashLayout()
- if (!layout) {
- transport.showCommandAlert('固件大小', `请选择 ${FLASH_SIZE_TEXT} .bin 文件`)
- return false
- }
- setState({
- bootloaderDetailText: '',
- bootloaderProgress: 0,
- bootloaderStatusText: '握手中',
- isBootloaderBusy: true
- })
- try {
- await handshakeUntilReady()
- setState({
- bootloaderDetailText: '编程解锁',
- bootloaderStatusText: '升级中'
- })
- assertAck(await sendBootloaderFrame(buildUnlockFrame(), 'Bootloader解锁', 'unlock'), '编程解锁')
- setState({
- bootloaderDetailText: '开启页擦除',
- bootloaderStatusText: '升级中'
- })
- assertAck(await sendBootloaderFrame(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 sendBootloaderFrame(
- buildProgramFrame(address, chunk),
- `编程 0x${toHex(address, 4)}`,
- 'program',
- PROGRAM_RESPONSE_TIMEOUT_MS
- )
- assertAck(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 sendBootloaderFrame(buildFlashCheckFrame(), '全Flash校验', 'flashCheck')
- await sendBootloaderFrame(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 sendBootloaderFrame(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 sendBootloaderFrame(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
- }
|