chore(kboot): add more logging (#1008)
* chore(kboot): add more logging * fix: add uncaughtException handler * fix: wait to prevent race condition * fix: don't close device after success left keyboard connection * revert: remove extra delay * revert: add back the waiting * fix: always initialize new KBoot instance when try to configure I2C * fix: increase the wait time between 2 IC2 reconnection * fix: timing and usb reconnection * fix: dont close kboot device * feat: append the WithKboot to the firmware upgrade methods * feat: revert back the blhost functionality
This commit is contained in:
committed by
László Monda
parent
8a7f30dbb1
commit
b53751b408
6
package-lock.json
generated
6
package-lock.json
generated
@@ -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": {
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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<CommandResponse> {
|
||||
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<BootloaderVersion> {
|
||||
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<void> {
|
||||
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<void> {
|
||||
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<void> {
|
||||
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<any> {
|
||||
logger('Start read memory %o', { startAddress, count });
|
||||
return this.peripheral.readMemory(startAddress, count);
|
||||
}
|
||||
|
||||
async writeMemory(options: DataOption): Promise<void> {
|
||||
logger('Start write memory %o', { options });
|
||||
return this.peripheral.writeMemory(options);
|
||||
}
|
||||
|
||||
@@ -133,6 +154,7 @@ export class KBoot {
|
||||
* Reset the bootloader
|
||||
*/
|
||||
async reset(): Promise<void> {
|
||||
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<void> {
|
||||
|
||||
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}`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<Buffer> {
|
||||
logger('start read from buffer %o', { bufferName, byte, timeout });
|
||||
return new Promise<Buffer>(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<CommandResponse> {
|
||||
logger('Start read next command response');
|
||||
const response = await this._readFromCommandStream();
|
||||
const commandResponse = decodeCommandResponse(response);
|
||||
logger('next command response: %o', commandResponse);
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
export * from './constants';
|
||||
export * from './uhk-blhost';
|
||||
export * from './uhk-hid-device';
|
||||
export * from './uhk-operations';
|
||||
export * from './util';
|
||||
|
||||
89
packages/uhk-usb/src/uhk-blhost.ts
Normal file
89
packages/uhk-usb/src/uhk-blhost.ts
Normal file
@@ -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<string>): Promise<void> {
|
||||
const self = this;
|
||||
return new Promise<void>((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<string>, maxTry = 100): Promise<void> {
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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');
|
||||
|
||||
@@ -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<any>): 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);
|
||||
|
||||
@@ -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'),
|
||||
|
||||
@@ -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/'});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user