From e3604af230bfeb163f3498681e1c855084daeafa Mon Sep 17 00:00:00 2001 From: Nerivec <62446222+Nerivec@users.noreply.github.com> Date: Tue, 29 Oct 2024 19:27:11 +0100 Subject: [PATCH] Add specific logic for Sonoff ZBDongle-E Router. --- README.md | 12 ++++---- package.json | 2 +- src/commands/bootloader/index.ts | 44 +++++++++++++++++++++++++- src/utils/bootloader.ts | 53 +++++++++++++++----------------- src/utils/cpc.ts | 8 ++--- src/utils/types.ts | 7 ++--- 6 files changed, 82 insertions(+), 44 deletions(-) diff --git a/README.md b/README.md index ab1e919..67b0a53 100644 --- a/README.md +++ b/README.md @@ -76,7 +76,7 @@ $ npm install -g ember-zli $ ember-zli COMMAND running command... $ ember-zli (--version) -ember-zli/2.6.2 win32-x64 node-v20.15.0 +ember-zli/2.6.3 win32-x64 node-v20.15.0 $ ember-zli --help [COMMAND] USAGE $ ember-zli COMMAND @@ -108,7 +108,7 @@ EXAMPLES $ ember-zli bootloader ``` -_See code: [src/commands/bootloader/index.ts](https://github.com/Nerivec/ember-zli/blob/v2.6.2/src/commands/bootloader/index.ts)_ +_See code: [src/commands/bootloader/index.ts](https://github.com/Nerivec/ember-zli/blob/v2.6.3/src/commands/bootloader/index.ts)_ ## `ember-zli help [COMMAND]` @@ -145,7 +145,7 @@ EXAMPLES $ ember-zli router ``` -_See code: [src/commands/router/index.ts](https://github.com/Nerivec/ember-zli/blob/v2.6.2/src/commands/router/index.ts)_ +_See code: [src/commands/router/index.ts](https://github.com/Nerivec/ember-zli/blob/v2.6.3/src/commands/router/index.ts)_ ## `ember-zli sniff` @@ -162,7 +162,7 @@ EXAMPLES $ ember-zli sniff ``` -_See code: [src/commands/sniff/index.ts](https://github.com/Nerivec/ember-zli/blob/v2.6.2/src/commands/sniff/index.ts)_ +_See code: [src/commands/sniff/index.ts](https://github.com/Nerivec/ember-zli/blob/v2.6.3/src/commands/sniff/index.ts)_ ## `ember-zli stack` @@ -179,7 +179,7 @@ EXAMPLES $ ember-zli stack ``` -_See code: [src/commands/stack/index.ts](https://github.com/Nerivec/ember-zli/blob/v2.6.2/src/commands/stack/index.ts)_ +_See code: [src/commands/stack/index.ts](https://github.com/Nerivec/ember-zli/blob/v2.6.3/src/commands/stack/index.ts)_ ## `ember-zli utils` @@ -196,7 +196,7 @@ EXAMPLES $ ember-zli utils ``` -_See code: [src/commands/utils/index.ts](https://github.com/Nerivec/ember-zli/blob/v2.6.2/src/commands/utils/index.ts)_ +_See code: [src/commands/utils/index.ts](https://github.com/Nerivec/ember-zli/blob/v2.6.3/src/commands/utils/index.ts)_ ## `ember-zli version` diff --git a/package.json b/package.json index b28632d..470e15e 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "ember-zli", "description": "Interact with EmberZNet-based adapters using zigbee-herdsman 'ember' driver", - "version": "2.6.2", + "version": "2.6.3", "author": "Nerivec", "bin": { "ember-zli": "bin/run.js" diff --git a/src/commands/bootloader/index.ts b/src/commands/bootloader/index.ts index 5709082..2c35639 100644 --- a/src/commands/bootloader/index.ts +++ b/src/commands/bootloader/index.ts @@ -15,6 +15,31 @@ import { AdapterModel, FirmwareVariant } from '../../utils/types.js' const SUPPORTED_VERSIONS_REGEX = /(7\.4\.\d\.\d)|(8\.0\.\d\.\d)/ const FIRMWARE_EXT = '.gbl' +const clearNVM3SonoffZBDongleE: () => Buffer = () => { + const start = 'eb17a603080000000000000300000000f40a0af41c00000000000000000000000000000000000000000000000000000000000000fd0303fd0480000000600b00' + const blankChunkStart = '01009ab2010000d0feffff0fffffffff0098' + const blankChunkLength = 8174 + const end = 'fc0404fc040000004b83c4aa' + + return Buffer.concat([ + Buffer.from(start, 'hex'), + Buffer.from(blankChunkStart, 'hex'), + Buffer.alloc(blankChunkLength, 0xff), + Buffer.from(blankChunkStart, 'hex'), + Buffer.alloc(blankChunkLength, 0xff), + Buffer.from(blankChunkStart, 'hex'), + Buffer.alloc(blankChunkLength, 0xff), + Buffer.from(blankChunkStart, 'hex'), + Buffer.alloc(blankChunkLength, 0xff), + Buffer.from(end, 'hex'), + ]) +} + +const CLEAR_NVM3_BUFFERS: Partial Buffer>> = { + 'Sonoff ZBDongle-E': clearNVM3SonoffZBDongleE, + 'Sonoff ZBDongle-E - ROUTER': clearNVM3SonoffZBDongleE, +} + export default class Bootloader extends Command { static override args = {} static override description = 'Interact with the Gecko bootloader in the adapter.' @@ -96,7 +121,7 @@ export default class Bootloader extends Command { choices: [ { name: 'Get info', value: BootloaderMenu.INFO }, { name: 'Update firmware', value: BootloaderMenu.UPLOAD_GBL }, - { name: 'Clear NVM3', value: BootloaderMenu.CLEAR_NVM3, disabled: gecko.adapterModel !== 'Sonoff ZBDongle-E' }, + { name: 'Clear NVM3', value: BootloaderMenu.CLEAR_NVM3, disabled: !this.supportsClearNVM3(gecko.adapterModel) }, { name: 'Exit bootloader', value: BootloaderMenu.RUN }, { name: 'Force close', value: -1 }, ], @@ -122,6 +147,9 @@ export default class Bootloader extends Command { return false } } + } else if (answer === BootloaderMenu.CLEAR_NVM3) { + // adapterModel is defined here since menu is disabled if not supported, same for the value in the object + firmware = CLEAR_NVM3_BUFFERS[gecko.adapterModel!]!() } return gecko.navigate(answer, firmware) @@ -235,4 +263,18 @@ export default class Bootloader extends Command { } } } + + private supportsClearNVM3(adapterModel?: AdapterModel): boolean { + if (!adapterModel) { + return false + } + + for (const key in CLEAR_NVM3_BUFFERS) { + if (key === adapterModel) { + return true + } + } + + return false + } } diff --git a/src/utils/bootloader.ts b/src/utils/bootloader.ts index ef6e42e..64432ab 100644 --- a/src/utils/bootloader.ts +++ b/src/utils/bootloader.ts @@ -71,11 +71,8 @@ const GBL_END_TAG = Buffer.from([0xfc, 0x04, 0x04, 0xfc]) const GBL_METADATA_TAG = Buffer.from([0xf6, 0x08, 0x08, 0xf6]) const VALID_FIRMWARE_CRC32 = 558161692 -const NVM3_INIT_START = - 'eb17a603080000000000000300000000f40a0af41c00000000000000000000000000000000000000000000000000000000000000fd0303fd0480000000600b00' -const NVM3_INIT_BLANK_CHUNK_START = '01009ab2010000d0feffff0fffffffff0098' -const NVM3_INIT_BLANK_CHUNK_LENGTH = 8174 -const NVM3_INIT_END = 'fc0404fc040000004b83c4aa' +const FORCE_RESET_SUPPORT_ADAPTERS: ReadonlyArray = ['Sonoff ZBDongle-E', 'Sonoff ZBDongle-E - ROUTER'] +const ALWAYS_FORCE_RESET_ADAPTERS: ReadonlyArray<(typeof FORCE_RESET_SUPPORT_ADAPTERS)[number]> = ['Sonoff ZBDongle-E - ROUTER'] export enum BootloaderEvent { FAILED = 'failed', @@ -109,13 +106,13 @@ export class GeckoBootloader extends EventEmitter { } | undefined - constructor(portConf: PortConf, adapter?: AdapterModel) { + constructor(portConf: PortConf, adapterModel?: AdapterModel) { super() this.state = BootloaderState.NOT_CONNECTED this.waiter = undefined this.portConf = portConf - this.adapterModel = adapter + this.adapterModel = adapterModel // override config to default for serial gecko bootloader this.transport = new Transport({ ...this.portConf, @@ -198,6 +195,13 @@ export class GeckoBootloader extends EventEmitter { } case BootloaderMenu.CLEAR_NVM3: { + if (firmware === undefined) { + logger.error(`Navigating to clear NVM3 requires a valid firmware.`, NS) + await this.transport.close(false) // don't emit closed since we're returning true which will close anyway + + return true + } + const confirmed = await confirm({ default: false, message: 'Confirm NVM3 clearing? (Cannot be undone.)', @@ -208,28 +212,16 @@ export class GeckoBootloader extends EventEmitter { return false } - return this.menuUploadGBL( - Buffer.concat([ - Buffer.from(NVM3_INIT_START, 'hex'), - Buffer.from(NVM3_INIT_BLANK_CHUNK_START, 'hex'), - Buffer.alloc(NVM3_INIT_BLANK_CHUNK_LENGTH, 0xff), - Buffer.from(NVM3_INIT_BLANK_CHUNK_START, 'hex'), - Buffer.alloc(NVM3_INIT_BLANK_CHUNK_LENGTH, 0xff), - Buffer.from(NVM3_INIT_BLANK_CHUNK_START, 'hex'), - Buffer.alloc(NVM3_INIT_BLANK_CHUNK_LENGTH, 0xff), - Buffer.from(NVM3_INIT_BLANK_CHUNK_START, 'hex'), - Buffer.alloc(NVM3_INIT_BLANK_CHUNK_LENGTH, 0xff), - Buffer.from(NVM3_INIT_END, 'hex'), - ]), - ) + return this.menuUploadGBL(firmware) } } } - public async resetByPattern(exit: boolean): Promise { + public async forceReset(exit: boolean): Promise { switch (this.adapterModel) { // TODO: support per adapter - case 'Sonoff ZBDongle-E': { + case 'Sonoff ZBDongle-E': + case 'Sonoff ZBDongle-E - ROUTER': { await this.transport.serialSet({ dtr: false, rts: true }) await this.transport.serialSet({ dtr: true, rts: false }, 100) @@ -381,14 +373,19 @@ export class GeckoBootloader extends EventEmitter { await this.transport.initPort() // on first knock only, try pattern reset if supported - if (!fail && this.adapterModel === 'Sonoff ZBDongle-E') { - const forceReset = await confirm({ message: 'Force reset into bootloader?', default: true }) + if (!fail && this.adapterModel && FORCE_RESET_SUPPORT_ADAPTERS.includes(this.adapterModel)) { + // XXX: always force reset Sonoff ZBDongle-E Router to prevent issues with EZSP 6.10.3 (can be removed once versions updated and no longer used) + const forceReset = + ALWAYS_FORCE_RESET_ADAPTERS.includes(this.adapterModel) || + (await confirm({ message: 'Force reset into bootloader?', default: true })) if (forceReset) { - await this.resetByPattern(false) + logger.debug(`Entering bootloader via force reset.`, NS) + + await this.forceReset(false) if (this.state === BootloaderState.IDLE) { - logger.debug(`Entered bootloader via pattern reset.`, NS) + // nothing else to do if already got the bl prompt return } } @@ -441,7 +438,7 @@ export class GeckoBootloader extends EventEmitter { // got menu back, failed to run logger.warning(`Failed to exit bootloader and run firmware. Trying pattern reset...`, NS) - await this.resetByPattern(true) + await this.forceReset(true) } return true diff --git a/src/utils/cpc.ts b/src/utils/cpc.ts index 00d626d..d5dc53a 100644 --- a/src/utils/cpc.ts +++ b/src/utils/cpc.ts @@ -26,7 +26,7 @@ import { CPC_SYSTEM_REBOOT_MODE_BOOTLOADER, } from './consts.js' import { Transport, TransportEvent } from './transport.js' -import { CpcSystemCommand, CpcSystemCommandId, CpcSystemStatus, Digit, FirmwareVersionShort, PortConf } from './types.js' +import { CpcSystemCommand, CpcSystemCommandId, CpcSystemStatus, FirmwareVersionShort, PortConf } from './types.js' import { computeCRC16 } from './utils.js' const NS = { namespace: 'cpc' } @@ -77,9 +77,9 @@ export class Cpc extends EventEmitter { } // const propertyId = result.payload.readUInt32LE(0) - const major = result.payload.readUInt32LE(4) as Digit - const minor = result.payload.readUInt32LE(8) as Digit - const patch = result.payload.readUInt32LE(12) as Digit + const major = result.payload.readUInt32LE(4) + const minor = result.payload.readUInt32LE(8) + const patch = result.payload.readUInt32LE(12) return `${major}.${minor}.${patch}` } diff --git a/src/utils/types.ts b/src/utils/types.ts index f571291..d98f6e0 100644 --- a/src/utils/types.ts +++ b/src/utils/types.ts @@ -3,8 +3,6 @@ import { EUI64 } from 'zigbee-herdsman/dist/zspec/tstypes.js' import { BAUDRATES } from './consts.js' -export type Digit = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 - export type AdapterModel = | 'Aeotec Zi-Stick (ZGA008)' | 'EasyIOT ZB-GW04 v1.1' @@ -15,6 +13,7 @@ export type AdapterModel = | 'SMLight SLZB07' | 'SMLight SLZB07mg24' | 'Sonoff ZBDongle-E' + | 'Sonoff ZBDongle-E - ROUTER' | 'SparkFun MGM240p' | 'TubeZB MGM24' | 'TubeZB MGM24PB' @@ -34,8 +33,8 @@ export type EmberFullVersion = { ezsp: number; revision: string } & EmberVersion export type ConfigValue = { [key: string]: string } export type FirmwareVariant = 'latest' | 'official' | 'recommended' | 'experimental' -export type FirmwareVersion = `${Digit}.${Digit}.${Digit}.${Digit}` -export type FirmwareVersionShort = `${Digit}.${Digit}.${Digit}` +export type FirmwareVersion = `${number}.${number}.${number}.${number}` +export type FirmwareVersionShort = `${number}.${number}.${number}` export type FirmwareFilename = `${string}.gbl` export type FirmwareURL = `https://${string}/${FirmwareFilename}`