diff --git a/package-lock.json b/package-lock.json index a36a3961..5923d8e5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1861,9 +1861,9 @@ "dev": true }, "@types/node-hid": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/@types/node-hid/-/node-hid-0.7.0.tgz", - "integrity": "sha512-P46Ed7R+/3peWShb7hNGD/ajvpMiY1tWKG35aQ3oPOxV2Nls6porDqXPvZT4qnx5T8hb6Ot5juxfQS87Tpjk4w==", + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/@types/node-hid/-/node-hid-0.7.2.tgz", + "integrity": "sha512-uWSDOBaJ0qCiuyx6NSvxFFgKeC9DEmK/G2Nm4jZp5yt1p6gg2Gg7nkrWGlzkaYiLf3YNCqm3X260PTYylIxfsA==", "dev": true }, "@types/q": { diff --git a/package.json b/package.json index f7afedb7..fe95362e 100644 --- a/package.json +++ b/package.json @@ -30,7 +30,7 @@ "@types/jsonfile": "5.0.0", "@types/lodash": "4.14.136", "@types/node": "8.0.53", - "@types/node-hid": "0.7.0", + "@types/node-hid": "0.7.2", "@types/request": "2.0.8", "@types/semver": "5.5.0", "@types/tmp": "0.0.33", @@ -91,6 +91,7 @@ "server:electron": "lerna exec --scope uhk-web npm run server:renderer", "electron": "lerna exec --scope uhk-agent npm start", "electron:spe": "lerna exec --scope uhk-agent npm run electron:spe", + "electron:kboot": "lerna exec --scope uhk-agent npm run electron:kboot", "pack": "node ./scripts/release.js", "sprites": "node ./scripts/generate-svg-sprites", "release": "node ./scripts/release.js", diff --git a/packages/kboot/src/kboot.ts b/packages/kboot/src/kboot.ts index 0e0e6d3f..23d38951 100644 --- a/packages/kboot/src/kboot.ts +++ b/packages/kboot/src/kboot.ts @@ -12,15 +12,19 @@ export class KBoot { } open(): void { + logger('Open peripheral'); this.peripheral.open(); } close(): void { + logger('Close peripheral'); this.peripheral.close(); } // ================= Read properties ================== async getProperty(property: Properties, memoryId = MemoryIds.Internal): Promise { + logger('Start read memory %o', { property, memoryId }); + const command: CommandOption = { command: Commands.GetProperty, params: [ @@ -32,14 +36,17 @@ export class KBoot { const response = await this.peripheral.sendCommand(command); if (response.tag !== ResponseTags.Property) { + logger('Response tag is not property response: %d', property); throw new Error('Response tag is not property response'); } if (response.code === ResponseCodes.UnknownProperty) { + logger('Unknown property %d', response.code); throw new Error('Unknown property!'); } if (response.code !== ResponseCodes.Success) { + logger('Unknown error %d', response.code); throw new Error(`Unknown error. Error code:${response.code}`); } @@ -47,6 +54,8 @@ export class KBoot { } async getBootloaderVersion(): Promise { + logger('Start to read Bootloader Version'); + const response = await this.getProperty(Properties.BootloaderVersion); const version: BootloaderVersion = { @@ -64,7 +73,9 @@ export class KBoot { // ================= End read properties ================== async flashSecurityDisable(key: number[]): Promise { + logger('Start flash security disable %o', { key }); if (key.length !== 8) { + logger('Error: Flash security key must be 8 byte. %o', key); throw new Error('Flash security key must be 8 byte'); } @@ -76,15 +87,18 @@ export class KBoot { const response = await this.peripheral.sendCommand(command); if (response.tag !== ResponseTags.Generic) { + logger('Response tag is not generic response: %d', response.tag); throw new Error('Response tag is not generic response'); } if (response.code !== ResponseCodes.Success) { + logger('Can not disable flash security: %d', response.code); throw new Error(`Can not disable flash security`); } } async flashEraseRegion(startAddress: number, count: number): Promise { + logger('Start flash erase region'); const command: CommandOption = { command: Commands.FlashEraseRegion, params: [ @@ -96,15 +110,18 @@ export class KBoot { const response = await this.peripheral.sendCommand(command); if (response.tag !== ResponseTags.Generic) { + logger('Response tag is not generic response: %d', response.tag); throw new Error('Response tag is not generic response'); } if (response.code !== ResponseCodes.Success) { - throw new Error(`Can not disable flash security`); + logger('Can not flash erase region: %d', response.code); + throw new Error(`Can not flash erase region`); } } async flashEraseAllUnsecure(): Promise { + logger('Start flash erase all unsecure'); const command: CommandOption = { command: Commands.FlashEraseAllUnsecure, params: [] @@ -113,19 +130,23 @@ export class KBoot { const response = await this.peripheral.sendCommand(command); if (response.tag !== ResponseTags.Generic) { + logger('Response tag is not generic response: %d', response.tag); throw new Error('Response tag is not generic response'); } if (response.code !== ResponseCodes.Success) { - throw new Error(`Can not disable flash security`); + logger('Can not flash erase all unsecure: %d', response.code); + throw new Error(`Can not flash erase all unsecure`); } } async readMemory(startAddress: number, count: number): Promise { + logger('Start read memory %o', { startAddress, count }); return this.peripheral.readMemory(startAddress, count); } async writeMemory(options: DataOption): Promise { + logger('Start write memory %o', { options }); return this.peripheral.writeMemory(options); } @@ -133,6 +154,7 @@ export class KBoot { * Reset the bootloader */ async reset(): Promise { + logger('Start reset the bootloader'); const command: CommandOption = { command: Commands.Reset, params: [] @@ -154,10 +176,12 @@ export class KBoot { } if (response.tag !== ResponseTags.Generic) { + logger('Response tag is not generic response: %d', response.tag); throw new Error('Response tag is not generic response'); } if (response.code !== ResponseCodes.Success) { + logger('Unknown error %d', response.code); throw new Error(`Unknown error. Error code:${response.code}`); } } @@ -168,8 +192,9 @@ export class KBoot { * @param [speed=64] - Speed of the I2C */ async configureI2c(address: number, speed = 64): Promise { - + logger('Start configure I2C', { address, speed }); if (address > 127) { + logger('Only 7-bit i2c address is supported'); throw new Error('Only 7-bit i2c address is supported'); } @@ -184,10 +209,12 @@ export class KBoot { const response = await this.peripheral.sendCommand(command); if (response.tag !== ResponseTags.Generic) { + logger('Response tag is not generic response: %d', response.tag); throw new Error('Response tag is not generic response'); } if (response.code !== ResponseCodes.Success) { + logger('Unknown error %d', response.code); throw new Error(`Unknown error. Error code:${response.code}`); } } diff --git a/packages/kboot/src/usb-peripheral.ts b/packages/kboot/src/usb-peripheral.ts index 01cdf411..f5c1cb66 100644 --- a/packages/kboot/src/usb-peripheral.ts +++ b/packages/kboot/src/usb-peripheral.ts @@ -55,6 +55,8 @@ export class UsbPeripheral implements Peripheral { close(): void { if (this._device) { this._device.close(); + this._device.removeAllListeners('data'); + this._device.removeAllListeners('error'); this._device = undefined; } } @@ -222,6 +224,7 @@ export class UsbPeripheral implements Peripheral { } private _readFromBuffer(bufferName: string, byte: number, timeout: number): Promise { + logger('start read from buffer %o', { bufferName, byte, timeout }); return new Promise(async (resolve, reject) => { const startTime = new Date(); while (startTime.getTime() + timeout > new Date().getTime()) { @@ -266,6 +269,7 @@ export class UsbPeripheral implements Peripheral { } private async _getNextCommandResponse(): Promise { + logger('Start read next command response'); const response = await this._readFromCommandStream(); const commandResponse = decodeCommandResponse(response); logger('next command response: %o', commandResponse); diff --git a/packages/uhk-agent/package.json b/packages/uhk-agent/package.json index 5c81533c..e69254fd 100644 --- a/packages/uhk-agent/package.json +++ b/packages/uhk-agent/package.json @@ -29,6 +29,7 @@ "scripts": { "start": "cross-env DEBUG=kboot* electron ./dist/electron-main.js", "electron:spe": "electron ./dist/electron-main.js --spe", + "electron:kboot": "cross-env DEBUG=kboot* electron ./dist/electron-main.js --useKboot", "build": "webpack && npm run install:build-deps && npm run build:usb && npm run download-firmware && npm run copy-to-tmp-folder", "build:usb": "electron-rebuild -w node-hid -p -m ./dist", "lint": "tslint --project tsconfig.json", diff --git a/packages/uhk-agent/src/electron-main.ts b/packages/uhk-agent/src/electron-main.ts index d5035166..09208f79 100644 --- a/packages/uhk-agent/src/electron-main.ts +++ b/packages/uhk-agent/src/electron-main.ts @@ -11,6 +11,7 @@ import * as commandLineArgs from 'command-line-args'; import { UhkHidDevice, UhkOperations } from 'uhk-usb'; // import { ElectronDataStorageRepositoryService } from './services/electron-datastorage-repository.service'; import { CommandLineArgs, LogRegExps } from 'uhk-common'; +import { UhkBlhost } from 'uhk-usb'; import { DeviceService } from './services/device.service'; import { logger } from './services/logger.service'; import { AppUpdateService } from './services/app-update.service'; @@ -22,7 +23,8 @@ import { loadWindowState, saveWindowState } from './util/window'; const optionDefinitions = [ {name: 'addons', type: Boolean}, - {name: 'spe', type: Boolean} // simulate privilege escalation error + {name: 'spe', type: Boolean}, // simulate privilege escalation error + {name: 'useKboot', type: Boolean} // If it is true use kboot package instead of blhost for firmware upgrade ]; const options: CommandLineArgs = commandLineArgs(optionDefinitions); @@ -36,6 +38,7 @@ let win: Electron.BrowserWindow; autoUpdater.logger = logger; let deviceService: DeviceService; +let uhkBlhost: UhkBlhost; let uhkHidDeviceService: UhkHidDevice; let uhkOperations: UhkOperations; let appUpdateService: AppUpdateService; @@ -100,8 +103,9 @@ function createWindow() { setMenu(win); uhkHidDeviceService = new UhkHidDevice(logger, options, packagesDir); - uhkOperations = new UhkOperations(logger, uhkHidDeviceService, packagesDir); - deviceService = new DeviceService(logger, win, uhkHidDeviceService, uhkOperations, packagesDir); + uhkBlhost = new UhkBlhost(logger, packagesDir); + uhkOperations = new UhkOperations(logger, uhkBlhost, uhkHidDeviceService, packagesDir); + deviceService = new DeviceService(logger, win, uhkHidDeviceService, uhkOperations, packagesDir, options); appUpdateService = new AppUpdateService(logger, win, app); appService = new AppService(logger, win, deviceService, options, uhkHidDeviceService); sudoService = new SudoService(logger, options); diff --git a/packages/uhk-agent/src/services/device.service.ts b/packages/uhk-agent/src/services/device.service.ts index 55825d10..9a034327 100644 --- a/packages/uhk-agent/src/services/device.service.ts +++ b/packages/uhk-agent/src/services/device.service.ts @@ -1,6 +1,7 @@ import { ipcMain } from 'electron'; import { isEqual } from 'lodash'; import { + CommandLineArgs, ConfigurationReply, DeviceConnectionState, FirmwareUpgradeIpcResponse, @@ -41,7 +42,8 @@ export class DeviceService { private win: Electron.BrowserWindow, private device: UhkHidDevice, private operations: UhkOperations, - private rootDir: string) { + private rootDir: string, + private options: CommandLineArgs) { this.startPollUhkDevice(); this.uhkDevicePoller() .catch(error => { @@ -182,15 +184,25 @@ export class DeviceService { const packageJson = await getPackageJsonFromPathAsync(firmwarePathData.packageJsonPath); this.logService.debug('New firmware version:', packageJson.firmwareVersion); - await this.operations.updateRightFirmware(firmwarePathData.rightFirmwarePath); - await this.operations.updateLeftModule(firmwarePathData.leftFirmwarePath); + if (this.options.useKboot) { + await this.operations.updateRightFirmwareWithKboot(firmwarePathData.rightFirmwarePath); + await this.operations.updateLeftModuleWithKboot(firmwarePathData.leftFirmwarePath); + } else { + await this.operations.updateRightFirmwareWithBlhost(firmwarePathData.rightFirmwarePath); + await this.operations.updateLeftModuleWithBlhost(firmwarePathData.leftFirmwarePath); + } } else { const packageJsonPath = path.join(this.rootDir, 'packages/firmware/package.json'); const packageJson = await getPackageJsonFromPathAsync(packageJsonPath); this.logService.debug('New firmware version:', packageJson.firmwareVersion); - await this.operations.updateRightFirmware(); - await this.operations.updateLeftModule(); + if (this.options.useKboot) { + await this.operations.updateRightFirmwareWithKboot(); + await this.operations.updateLeftModuleWithKboot(); + } else { + await this.operations.updateRightFirmwareWithBlhost(); + await this.operations.updateLeftModuleWithBlhost(); + } } response.success = true; @@ -220,7 +232,11 @@ export class DeviceService { try { await this.stopPollUhkDevice(); - await this.operations.updateRightFirmware(); + if (this.options.useKboot) { + await this.operations.updateRightFirmwareWithKboot(); + } else { + await this.operations.updateRightFirmwareWithBlhost(); + } response.modules = await this.getHardwareModules(false); response.success = true; @@ -270,19 +286,21 @@ export class DeviceService { while (true) { if (this._pollerAllowed) { - this._uhkDevicePolling = true; + try { - const state = await this.device.getDeviceConnectionStateAsync(); - if (!isEqual(state, savedState)) { - savedState = state; - this.win.webContents.send(IpcEvents.device.deviceConnectionStateChanged, state); - this.logService.info('[DeviceService] Device connection state changed to:', state); + const state = await this.device.getDeviceConnectionStateAsync(); + if (!isEqual(state, savedState)) { + savedState = state; + this.win.webContents.send(IpcEvents.device.deviceConnectionStateChanged, state); + this.logService.info('[DeviceService] Device connection state changed to:', state); + } + } catch (err) { + this.logService.error('[DeviceService] Device connection state query error', err); } - - this._uhkDevicePolling = false; } + this._uhkDevicePolling = false; await snooze(250); } } diff --git a/packages/uhk-common/src/models/command-line-args.ts b/packages/uhk-common/src/models/command-line-args.ts index 1e53fca3..fec29e8d 100644 --- a/packages/uhk-common/src/models/command-line-args.ts +++ b/packages/uhk-common/src/models/command-line-args.ts @@ -7,4 +7,8 @@ export interface CommandLineArgs { * simulate privilege escalation error */ spe?: boolean; + /** + * If it is true use kboot package instead of blhost for firmware upgrade + */ + useKboot?: boolean; } diff --git a/packages/uhk-usb/src/constants.ts b/packages/uhk-usb/src/constants.ts index 231c7098..176b8241 100644 --- a/packages/uhk-usb/src/constants.ts +++ b/packages/uhk-usb/src/constants.ts @@ -71,9 +71,9 @@ export enum EnumerationNameToProductId { } export enum ModuleSlotToI2cAddress { - leftHalf = 0x10, - leftModule = 0x20, - rightModule = 0x30 + leftHalf = '0x10', + leftModule = '0x20', + rightModule = '0x30' } export enum ModuleSlotToId { diff --git a/packages/uhk-usb/src/index.ts b/packages/uhk-usb/src/index.ts index bb17b021..7ebda342 100644 --- a/packages/uhk-usb/src/index.ts +++ b/packages/uhk-usb/src/index.ts @@ -1,4 +1,5 @@ export * from './constants'; +export * from './uhk-blhost'; export * from './uhk-hid-device'; export * from './uhk-operations'; export * from './util'; diff --git a/packages/uhk-usb/src/uhk-blhost.ts b/packages/uhk-usb/src/uhk-blhost.ts new file mode 100644 index 00000000..8d10b515 --- /dev/null +++ b/packages/uhk-usb/src/uhk-blhost.ts @@ -0,0 +1,89 @@ +import * as path from 'path'; +import { spawn } from 'child_process'; +import { LogService } from 'uhk-common'; +import { retry } from './util'; + +export class UhkBlhost { + private blhostPath: string; + + constructor(private logService: LogService, + private rootDir: string) { + } + + public async runBlhostCommand(params: Array): Promise { + const self = this; + return new Promise((resolve, reject) => { + const blhostPath = this.getBlhostPath(); + self.logService.debug(`[blhost] RUN: ${blhostPath} ${params.join(' ')}`); + const childProcess = spawn(`"${blhostPath}"`, params, {shell: true}); + let finished = false; + + childProcess.stdout.on('data', data => { + self.logService.debug(`[blhost] STDOUT: ${data}`); + }); + + childProcess.stderr.on('data', data => { + self.logService.error(`[blhost] STDERR: ${data}`); + }); + + childProcess.on('close', code => { + self.logService.debug(`[blhost] CLOSE_CODE: ${code}`); + finish(code); + }); + + childProcess.on('exit', code => { + self.logService.debug(`[blhost] EXIT_CODE: ${code}`); + finish(code); + }); + + childProcess.on('error', err => { + self.logService.debug(`[blhost] ERROR: ${err}`); + }); + + function finish(code) { + if (finished) { + return; + } + + finished = true; + + self.logService.debug(`[blhost] FINISHED: ${code}`); + + if (code !== 0) { + return reject(new Error(`blhost error code:${code}`)); + } + + resolve(); + } + }); + } + + public async runBlhostCommandRetry(params: Array, maxTry = 100): Promise { + return await retry(async () => await this.runBlhostCommand(params), maxTry, this.logService); + } + + private getBlhostPath(): string { + if (this.blhostPath) { + return this.blhostPath; + } + + let blhostPath; + switch (process.platform) { + case 'linux': + blhostPath = 'linux/x86_64/blhost'; + break; + case 'darwin': + blhostPath = 'mac/blhost'; + break; + case 'win32': + blhostPath = 'win/blhost.exe'; + break; + default: + throw new Error(`Could not find blhost path. Unknown platform:${process.platform}`); + } + + this.blhostPath = path.join(this.rootDir, `packages/blhost/${blhostPath}`); + + return this.blhostPath; + } +} diff --git a/packages/uhk-usb/src/uhk-hid-device.ts b/packages/uhk-usb/src/uhk-hid-device.ts index c7e493c7..a30b13e3 100644 --- a/packages/uhk-usb/src/uhk-hid-device.ts +++ b/packages/uhk-usb/src/uhk-hid-device.ts @@ -208,7 +208,7 @@ export class UhkHidDevice { while (new Date().getTime() - startTime.getTime() < 20000) { const devs = devices(); - this.logService.silly('[UhkHidDevice] reenumeration devices', devs); + this.logService.debug('[UhkHidDevice] reenumeration devices', devs); const inBootloaderMode = devs.some((x: Device) => x.vendorId === Constants.VENDOR_ID && @@ -219,7 +219,7 @@ export class UhkHidDevice { return; } - this.logService.silly(`[UhkHidDevice] Could not find reenumerated device: ${reenumMode}. Waiting...`); + this.logService.debug(`[UhkHidDevice] Could not find reenumerated device: ${reenumMode}. Waiting...`); await snooze(100); if (!jumped) { @@ -232,7 +232,7 @@ export class UhkHidDevice { device.close(); jumped = true; } else { - this.logService.silly(`[UhkHidDevice] USB[T]: Enumerate device is not ready yet}`); + this.logService.debug(`[UhkHidDevice] USB[T]: Enumerate device is not ready yet}`); } } } @@ -249,7 +249,7 @@ export class UhkHidDevice { if (command === KbootCommands.idle) { transfer = Buffer.from([UsbCommand.SendKbootCommandToModule, command]); } else { - transfer = Buffer.from([UsbCommand.SendKbootCommandToModule, command, module]); + transfer = Buffer.from([UsbCommand.SendKbootCommandToModule, command, Number.parseInt(module, 16)]); } await retry(async () => await this.write(transfer), maxTry, this.logService); } diff --git a/packages/uhk-usb/src/uhk-operations.ts b/packages/uhk-usb/src/uhk-operations.ts index bfa29145..02844dc9 100644 --- a/packages/uhk-usb/src/uhk-operations.ts +++ b/packages/uhk-usb/src/uhk-operations.ts @@ -13,6 +13,7 @@ import { import * as path from 'path'; import * as fs from 'fs'; import * as os from 'os'; +import { UhkBlhost } from './uhk-blhost'; import { UhkHidDevice } from './uhk-hid-device'; import { readBootloaderFirmwareFromHexFileAsync, snooze, waitForDevice } from './util'; import { ConfigBufferId, convertBufferToIntArray, DevicePropertyIds, getTransferBuffers, UsbCommand } from '../index'; @@ -20,11 +21,67 @@ import { LoadConfigurationsResult } from './models/load-configurations-result'; export class UhkOperations { constructor(private logService: LogService, + private blhost: UhkBlhost, private device: UhkHidDevice, private rootDir: string) { } - public async updateRightFirmware(firmwarePath = this.getFirmwarePath()) { + public async updateRightFirmwareWithBlhost(firmwarePath = this.getFirmwarePath()) { + this.logService.debug(`[UhkOperations] Operating system: ${os.type()} ${os.release()} ${os.arch()}`); + this.logService.debug('[UhkOperations] Start flashing right firmware'); + const prefix = [`--usb 0x1d50,0x${EnumerationNameToProductId.bootloader.toString(16)}`]; + + await this.device.reenumerate(EnumerationModes.Bootloader); + this.device.close(); + await this.blhost.runBlhostCommand([...prefix, 'flash-security-disable', '0403020108070605']); + await this.blhost.runBlhostCommand([...prefix, 'flash-erase-region', '0xc000', '475136']); + await this.blhost.runBlhostCommand([...prefix, 'flash-image', `"${firmwarePath}"`]); + await this.blhost.runBlhostCommand([...prefix, 'reset']); + this.logService.debug('[UhkOperations] Right firmware successfully flashed'); + } + + public async updateLeftModuleWithBlhost(firmwarePath = this.getLeftModuleFirmwarePath()) { + this.logService.debug('[UhkOperations] Start flashing left module firmware'); + + const prefix = [`--usb 0x1d50,0x${EnumerationNameToProductId.buspal.toString(16)}`]; + const buspalPrefix = [...prefix, `--buspal i2c,${ModuleSlotToI2cAddress.leftHalf}`]; + + await this.device.reenumerate(EnumerationModes.NormalKeyboard); + this.device.close(); + await snooze(1000); + await this.device.sendKbootCommandToModule(ModuleSlotToI2cAddress.leftHalf, KbootCommands.ping, 100); + await snooze(1000); + await this.device.jumpToBootloaderModule(ModuleSlotToId.leftHalf); + this.device.close(); + + const leftModuleBricked = await this.waitForKbootIdle(); + if (!leftModuleBricked) { + const msg = '[UhkOperations] Couldn\'t connect to the left keyboard half.'; + this.logService.error(msg); + throw new Error(msg); + } + + await this.device.reenumerate(EnumerationModes.Buspal); + this.device.close(); + await this.blhost.runBlhostCommandRetry([...buspalPrefix, 'get-property', '1']); + await this.blhost.runBlhostCommand([...buspalPrefix, 'flash-erase-all-unsecure']); + await this.blhost.runBlhostCommand([...buspalPrefix, 'write-memory', '0x0', `"${firmwarePath}"`]); + await this.blhost.runBlhostCommand([...prefix, 'reset']); + await snooze(1000); + await this.device.reenumerate(EnumerationModes.NormalKeyboard); + this.device.close(); + await snooze(1000); + await this.device.sendKbootCommandToModule(ModuleSlotToI2cAddress.leftHalf, KbootCommands.reset, 100); + this.device.close(); + await snooze(1000); + await this.device.sendKbootCommandToModule(ModuleSlotToI2cAddress.leftHalf, KbootCommands.idle); + this.device.close(); + + this.logService.debug('[UhkOperations] Left firmware successfully flashed'); + this.logService.debug('[UhkOperations] Both left and right firmwares successfully flashed'); + } + + public async updateRightFirmwareWithKboot(firmwarePath = this.getFirmwarePath()) { this.logService.debug(`[UhkOperations] Operating system: ${os.type()} ${os.release()} ${os.arch()}`); this.logService.debug('[UhkOperations] Start flashing right firmware'); @@ -58,9 +115,10 @@ export class UhkOperations { this.logService.debug('[UhkOperations] Right firmware successfully flashed'); } - public async updateLeftModule(firmwarePath = this.getLeftModuleFirmwarePath()) { + public async updateLeftModuleWithKboot(firmwarePath = this.getLeftModuleFirmwarePath()) { this.logService.debug('[UhkOperations] Start flashing left module firmware'); + const i2cAddressOfLeftModule = Number.parseInt(ModuleSlotToI2cAddress.leftHalf, 16); await this.device.reenumerate(EnumerationModes.NormalKeyboard); this.device.close(); await snooze(1000); @@ -86,28 +144,31 @@ export class UhkOperations { while (true) { try { this.logService.debug('[UhkOperations] Try to connect to the LEFT keyboard'); - await kboot.configureI2c(ModuleSlotToI2cAddress.leftHalf); + await kboot.configureI2c(i2cAddressOfLeftModule); await kboot.getProperty(Properties.BootloaderVersion); break; } catch { if (tryCount > 100) { throw new Error('Can not connect to the LEFT keyboard'); } - } finally { - kboot.close(); + await snooze(2000); } - await snooze(100); tryCount++; } + + // https://github.com/node-hid/node-hid/issues/230 + this.logService.debug('[UhkOperations] Wait 1 sec to prevent node-hid race condition'); + await snooze(1000); + this.logService.debug('[UhkOperations] Flash erase all on LEFT keyboard'); - await kboot.configureI2c(ModuleSlotToI2cAddress.leftHalf); + await kboot.configureI2c(i2cAddressOfLeftModule); await kboot.flashEraseAllUnsecure(); this.logService.debug('[UhkOperations] Read LEFT firmware from file'); const configData = fs.readFileSync(firmwarePath); this.logService.debug('[UhkOperations] Write memory'); - await kboot.configureI2c(ModuleSlotToI2cAddress.leftHalf); + await kboot.configureI2c(i2cAddressOfLeftModule); await kboot.writeMemory({ startAddress: 0, data: configData }); this.logService.debug('[UhkOperations] Reset LEFT keyboard'); diff --git a/packages/usb/kboot-firmware-upgrade.ts b/packages/usb/kboot-firmware-upgrade.ts index a0ffd52b..aaf850d4 100644 --- a/packages/usb/kboot-firmware-upgrade.ts +++ b/packages/usb/kboot-firmware-upgrade.ts @@ -7,9 +7,18 @@ const rootDir = path.join(__dirname, '../../tmp'); const uhkHidDevice = new UhkHidDevice(logService, {}, rootDir); const uhkOperations = new UhkOperations(logService, uhkHidDevice, rootDir); +process.on('uncaughtException', error => { + console.error('uncaughtException', error); + process.exit(1); +}); + +process.on('unhandledRejection', (reason: any, promise: Promise): void => { + console.error('unhandledRejection', { reason, promise }); +}); + uhkOperations - .updateRightFirmware() - .then(() => uhkOperations.updateLeftModule()) + .updateRightFirmwareWithKboot() + .then(() => uhkOperations.updateLeftModuleWithKboot()) .then(() => console.log('Firmware upgrade finished')) .catch(error => { console.error(error); diff --git a/scripts/copy-to-tmp-folder.js b/scripts/copy-to-tmp-folder.js index a0917088..5edeba34 100644 --- a/scripts/copy-to-tmp-folder.js +++ b/scripts/copy-to-tmp-folder.js @@ -8,6 +8,13 @@ const copyOptions = { const promises = []; +promises.push( + fse.copy( + path.join(__dirname, '../packages/usb/blhost'), + path.join(__dirname, '../tmp/packages/blhost'), + copyOptions) +); + promises.push( fse.copy( path.join(__dirname, '../rules'), diff --git a/scripts/release.js b/scripts/release.js index 99f85a71..362cf310 100644 --- a/scripts/release.js +++ b/scripts/release.js @@ -96,7 +96,7 @@ if (TEST_BUILD || gitTag) { const rootJson = require('../package.json'); update2ndPackageJson(rootJson); - // Add firmware to extra resources + // Add firmware and blhost to extra resources const extractedFirmwareDir = path.join(__dirname, '../tmp/packages'); extraResources.push({from: extractedFirmwareDir, to: 'packages/'});