Skip to content

Commit

Permalink
Improve bootloader reliability over TCP
Browse files Browse the repository at this point in the history
  • Loading branch information
Nerivec committed Nov 6, 2024
1 parent 0e39fb0 commit 364a5b6
Show file tree
Hide file tree
Showing 4 changed files with 38 additions and 25 deletions.
12 changes: 6 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ $ npm install -g ember-zli
$ ember-zli COMMAND
running command...
$ ember-zli (--version)
ember-zli/2.8.1 win32-x64 node-v22.11.0
ember-zli/2.8.2 win32-x64 node-v22.11.0
$ ember-zli --help [COMMAND]
USAGE
$ ember-zli COMMAND
Expand Down Expand Up @@ -60,7 +60,7 @@ EXAMPLES
$ ember-zli bootloader
```

_See code: [src/commands/bootloader/index.ts](https://github.com/Nerivec/ember-zli/blob/v2.8.1/src/commands/bootloader/index.ts)_
_See code: [src/commands/bootloader/index.ts](https://github.com/Nerivec/ember-zli/blob/v2.8.2/src/commands/bootloader/index.ts)_

## `ember-zli help [COMMAND]`

Expand Down Expand Up @@ -97,7 +97,7 @@ EXAMPLES
$ ember-zli router
```

_See code: [src/commands/router/index.ts](https://github.com/Nerivec/ember-zli/blob/v2.8.1/src/commands/router/index.ts)_
_See code: [src/commands/router/index.ts](https://github.com/Nerivec/ember-zli/blob/v2.8.2/src/commands/router/index.ts)_

## `ember-zli sniff`

Expand All @@ -114,7 +114,7 @@ EXAMPLES
$ ember-zli sniff
```

_See code: [src/commands/sniff/index.ts](https://github.com/Nerivec/ember-zli/blob/v2.8.1/src/commands/sniff/index.ts)_
_See code: [src/commands/sniff/index.ts](https://github.com/Nerivec/ember-zli/blob/v2.8.2/src/commands/sniff/index.ts)_

## `ember-zli stack`

Expand All @@ -131,7 +131,7 @@ EXAMPLES
$ ember-zli stack
```

_See code: [src/commands/stack/index.ts](https://github.com/Nerivec/ember-zli/blob/v2.8.1/src/commands/stack/index.ts)_
_See code: [src/commands/stack/index.ts](https://github.com/Nerivec/ember-zli/blob/v2.8.2/src/commands/stack/index.ts)_

## `ember-zli utils`

Expand All @@ -148,7 +148,7 @@ EXAMPLES
$ ember-zli utils
```

_See code: [src/commands/utils/index.ts](https://github.com/Nerivec/ember-zli/blob/v2.8.1/src/commands/utils/index.ts)_
_See code: [src/commands/utils/index.ts](https://github.com/Nerivec/ember-zli/blob/v2.8.2/src/commands/utils/index.ts)_

## `ember-zli version`

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "ember-zli",
"description": "Interact with EmberZNet-based adapters using zigbee-herdsman 'ember' driver",
"version": "2.8.1",
"version": "2.8.2",
"author": "Nerivec",
"bin": {
"ember-zli": "bin/run.js"
Expand Down
2 changes: 1 addition & 1 deletion src/commands/bootloader/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ export default class Bootloader extends Command {
{ name: 'Get info', value: BootloaderMenu.INFO },
{ name: 'Update firmware', value: BootloaderMenu.UPLOAD_GBL },
{ name: 'Clear NVM3', value: BootloaderMenu.CLEAR_NVM3, disabled: !this.supportsClearNVM3(gecko.adapterModel) },
{ name: 'Exit bootloader', value: BootloaderMenu.RUN },
{ name: 'Exit bootloader (run firmware)', value: BootloaderMenu.RUN },
{ name: 'Force close', value: -1 },
],
message: 'Menu',
Expand Down
47 changes: 30 additions & 17 deletions src/utils/bootloader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,17 +45,22 @@ export enum BootloaderMenu {

const CARRIAGE_RETURN = 0x0d
const NEWLINE = 0x0a

const BOOTLOADER_KNOCK = Buffer.from([NEWLINE])
const BOOTLOADER_MENU_UPLOAD_GBL = Buffer.from([BootloaderMenu.UPLOAD_GBL])
const BOOTLOADER_MENU_RUN = Buffer.from([BootloaderMenu.RUN])
const BOOTLOADER_MENU_INFO = Buffer.from([BootloaderMenu.INFO])

const BOOTLOADER_PROMPT = Buffer.from('BL >', 'ascii')
const BOOTLOADER_INFO = Buffer.from('Bootloader v', 'ascii')
const BOOTLOADER_VERSION = Buffer.from('Bootloader v', 'ascii')
const BOOTLOADER_BEGIN_UPLOAD = Buffer.from('begin upload', 'ascii')
const BOOTLOADER_UPLOAD_COMPLETE = Buffer.from('Serial upload complete', 'ascii')
const BOOTLOADER_UPLOAD_ABORTED = Buffer.from('Serial upload aborted', 'ascii')

const BOOTLOADER_KNOCK_TIMEOUT = 2000
const BOOTLOADER_UPLOAD_TIMEOUT = 1500000
const BOOTLOADER_UPLOAD_EXIT_TIMEOUT = 500
const BOOTLOADER_CMD_EXEC_TIMEOUT = 200
const BOOTLOADER_KNOCK_TIMEOUT = 1500
const BOOTLOADER_UPLOAD_TIMEOUT = 1800000
const BOOTLOADER_UPLOAD_EXIT_TIMEOUT = 1500
const BOOTLOADER_CMD_EXEC_TIMEOUT = 500

const GBL_START_TAG = Buffer.from([0xeb, 0x17, 0xa6, 0x03])
/** Contains length+CRC32 and possibly padding after this. */
Expand Down Expand Up @@ -363,7 +368,7 @@ export class GeckoBootloader extends EventEmitter<GeckoBootloaderEventMap> {
}

private async knock(fail: boolean): Promise<void> {
logger.debug(`Knocking...`, NS)
logger.info(fail ? `Entering bootloader...` : `Trying to enter bootloader...`, NS)

try {
await this.transport.initPort()
Expand All @@ -376,7 +381,7 @@ export class GeckoBootloader extends EventEmitter<GeckoBootloaderEventMap> {
(await confirm({ message: 'Force reset into bootloader?', default: true }))

if (forceReset) {
logger.debug(`Entering bootloader via force reset.`, NS)
logger.debug(`Entering bootloader via force reset...`, NS)

await this.forceReset(false)

Expand All @@ -401,7 +406,9 @@ export class GeckoBootloader extends EventEmitter<GeckoBootloaderEventMap> {

res = await this.waitForState(BootloaderState.IDLE, BOOTLOADER_KNOCK_TIMEOUT, fail && i == 2)

if (!res && i == 1 && this.transport.isSerial) {
if (res) {
break
} else if (i == 1 && this.transport.isSerial) {
// if failed first attempt, try second time with RTS/CTS enabled
await this.transport.serialSet({ rts: true, cts: true })
}
Expand All @@ -423,7 +430,7 @@ export class GeckoBootloader extends EventEmitter<GeckoBootloaderEventMap> {

this.state = BootloaderState.GETTING_INFO

await this.transport.write(Buffer.from([BootloaderMenu.INFO]))
await this.transport.write(BOOTLOADER_MENU_INFO)

await this.waitForState(BootloaderState.GOT_INFO, BOOTLOADER_CMD_EXEC_TIMEOUT)

Expand All @@ -435,9 +442,9 @@ export class GeckoBootloader extends EventEmitter<GeckoBootloaderEventMap> {

this.state = BootloaderState.RUNNING

await this.transport.write(Buffer.from([BootloaderMenu.RUN]))
await this.transport.write(BOOTLOADER_MENU_RUN)

const res = await this.waitForState(BootloaderState.IDLE, BOOTLOADER_CMD_EXEC_TIMEOUT, false)
const res = await this.waitForState(BootloaderState.IDLE, BOOTLOADER_CMD_EXEC_TIMEOUT, false, true)

if (res) {
// got menu back, failed to run
Expand All @@ -456,7 +463,7 @@ export class GeckoBootloader extends EventEmitter<GeckoBootloaderEventMap> {

this.state = BootloaderState.BEGIN_UPLOAD

await this.transport.write(Buffer.from([BootloaderMenu.UPLOAD_GBL])) // start upload
await this.transport.write(BOOTLOADER_MENU_UPLOAD_GBL) // start upload
await this.waitForState(BootloaderState.UPLOADING, BOOTLOADER_UPLOAD_EXIT_TIMEOUT)
await this.waitForState(BootloaderState.UPLOADED, BOOTLOADER_UPLOAD_TIMEOUT)

Expand Down Expand Up @@ -505,15 +512,18 @@ export class GeckoBootloader extends EventEmitter<GeckoBootloaderEventMap> {
logger.error(`Firmware upload aborted.`, NS)
} else if (received.includes(BOOTLOADER_UPLOAD_COMPLETE)) {
logger.info(`Firmware upload completed.`, NS)
} else if (received.includes(BOOTLOADER_PROMPT)) {
}

// always check if got back prompt already (can be in same tx as above)
if (received.includes(BOOTLOADER_PROMPT)) {
this.resolveState(BootloaderState.IDLE)
}

break
}

case BootloaderState.RUNNING: {
const blv = received.indexOf(BOOTLOADER_INFO)
const blv = received.indexOf(BOOTLOADER_VERSION)

if (blv !== -1) {
const [blInfo] = this.readBootloaderInfo(received, blv)
Expand All @@ -527,7 +537,7 @@ export class GeckoBootloader extends EventEmitter<GeckoBootloaderEventMap> {
}

case BootloaderState.GETTING_INFO: {
const blv = received.indexOf(BOOTLOADER_INFO)
const blv = received.indexOf(BOOTLOADER_VERSION)

if (blv !== -1) {
this.resolveState(BootloaderState.GOT_INFO)
Expand Down Expand Up @@ -621,7 +631,7 @@ export class GeckoBootloader extends EventEmitter<GeckoBootloaderEventMap> {
this.state = state
}

private waitForState(state: BootloaderState, timeout: number = 5000, fail: boolean = true): Promise<boolean> {
private waitForState(state: BootloaderState, timeout: number = 5000, fail: boolean = true, expectingFail: boolean = false): Promise<boolean> {
return new Promise<boolean>((resolve) => {
this.waiter = {
resolve,
Expand All @@ -635,7 +645,10 @@ export class GeckoBootloader extends EventEmitter<GeckoBootloaderEventMap> {
return
}

logger.debug(msg, NS)
if (!expectingFail) {
logger.debug(msg, NS)
}

resolve(false)
this.waiter = undefined
}, timeout),
Expand Down

0 comments on commit 364a5b6

Please sign in to comment.