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:
Róbert Kiss
2019-09-13 10:17:44 +02:00
committed by László Monda
parent 8a7f30dbb1
commit b53751b408
16 changed files with 268 additions and 42 deletions

6
package-lock.json generated
View File

@@ -1861,9 +1861,9 @@
"dev": true "dev": true
}, },
"@types/node-hid": { "@types/node-hid": {
"version": "0.7.0", "version": "0.7.2",
"resolved": "https://registry.npmjs.org/@types/node-hid/-/node-hid-0.7.0.tgz", "resolved": "https://registry.npmjs.org/@types/node-hid/-/node-hid-0.7.2.tgz",
"integrity": "sha512-P46Ed7R+/3peWShb7hNGD/ajvpMiY1tWKG35aQ3oPOxV2Nls6porDqXPvZT4qnx5T8hb6Ot5juxfQS87Tpjk4w==", "integrity": "sha512-uWSDOBaJ0qCiuyx6NSvxFFgKeC9DEmK/G2Nm4jZp5yt1p6gg2Gg7nkrWGlzkaYiLf3YNCqm3X260PTYylIxfsA==",
"dev": true "dev": true
}, },
"@types/q": { "@types/q": {

View File

@@ -30,7 +30,7 @@
"@types/jsonfile": "5.0.0", "@types/jsonfile": "5.0.0",
"@types/lodash": "4.14.136", "@types/lodash": "4.14.136",
"@types/node": "8.0.53", "@types/node": "8.0.53",
"@types/node-hid": "0.7.0", "@types/node-hid": "0.7.2",
"@types/request": "2.0.8", "@types/request": "2.0.8",
"@types/semver": "5.5.0", "@types/semver": "5.5.0",
"@types/tmp": "0.0.33", "@types/tmp": "0.0.33",
@@ -91,6 +91,7 @@
"server:electron": "lerna exec --scope uhk-web npm run server:renderer", "server:electron": "lerna exec --scope uhk-web npm run server:renderer",
"electron": "lerna exec --scope uhk-agent npm start", "electron": "lerna exec --scope uhk-agent npm start",
"electron:spe": "lerna exec --scope uhk-agent npm run electron:spe", "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", "pack": "node ./scripts/release.js",
"sprites": "node ./scripts/generate-svg-sprites", "sprites": "node ./scripts/generate-svg-sprites",
"release": "node ./scripts/release.js", "release": "node ./scripts/release.js",

View File

@@ -12,15 +12,19 @@ export class KBoot {
} }
open(): void { open(): void {
logger('Open peripheral');
this.peripheral.open(); this.peripheral.open();
} }
close(): void { close(): void {
logger('Close peripheral');
this.peripheral.close(); this.peripheral.close();
} }
// ================= Read properties ================== // ================= Read properties ==================
async getProperty(property: Properties, memoryId = MemoryIds.Internal): Promise<CommandResponse> { async getProperty(property: Properties, memoryId = MemoryIds.Internal): Promise<CommandResponse> {
logger('Start read memory %o', { property, memoryId });
const command: CommandOption = { const command: CommandOption = {
command: Commands.GetProperty, command: Commands.GetProperty,
params: [ params: [
@@ -32,14 +36,17 @@ export class KBoot {
const response = await this.peripheral.sendCommand(command); const response = await this.peripheral.sendCommand(command);
if (response.tag !== ResponseTags.Property) { if (response.tag !== ResponseTags.Property) {
logger('Response tag is not property response: %d', property);
throw new Error('Response tag is not property response'); throw new Error('Response tag is not property response');
} }
if (response.code === ResponseCodes.UnknownProperty) { if (response.code === ResponseCodes.UnknownProperty) {
logger('Unknown property %d', response.code);
throw new Error('Unknown property!'); throw new Error('Unknown property!');
} }
if (response.code !== ResponseCodes.Success) { if (response.code !== ResponseCodes.Success) {
logger('Unknown error %d', response.code);
throw new Error(`Unknown error. Error code:${response.code}`); throw new Error(`Unknown error. Error code:${response.code}`);
} }
@@ -47,6 +54,8 @@ export class KBoot {
} }
async getBootloaderVersion(): Promise<BootloaderVersion> { async getBootloaderVersion(): Promise<BootloaderVersion> {
logger('Start to read Bootloader Version');
const response = await this.getProperty(Properties.BootloaderVersion); const response = await this.getProperty(Properties.BootloaderVersion);
const version: BootloaderVersion = { const version: BootloaderVersion = {
@@ -64,7 +73,9 @@ export class KBoot {
// ================= End read properties ================== // ================= End read properties ==================
async flashSecurityDisable(key: number[]): Promise<void> { async flashSecurityDisable(key: number[]): Promise<void> {
logger('Start flash security disable %o', { key });
if (key.length !== 8) { 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'); throw new Error('Flash security key must be 8 byte');
} }
@@ -76,15 +87,18 @@ export class KBoot {
const response = await this.peripheral.sendCommand(command); const response = await this.peripheral.sendCommand(command);
if (response.tag !== ResponseTags.Generic) { if (response.tag !== ResponseTags.Generic) {
logger('Response tag is not generic response: %d', response.tag);
throw new Error('Response tag is not generic response'); throw new Error('Response tag is not generic response');
} }
if (response.code !== ResponseCodes.Success) { if (response.code !== ResponseCodes.Success) {
logger('Can not disable flash security: %d', response.code);
throw new Error(`Can not disable flash security`); throw new Error(`Can not disable flash security`);
} }
} }
async flashEraseRegion(startAddress: number, count: number): Promise<void> { async flashEraseRegion(startAddress: number, count: number): Promise<void> {
logger('Start flash erase region');
const command: CommandOption = { const command: CommandOption = {
command: Commands.FlashEraseRegion, command: Commands.FlashEraseRegion,
params: [ params: [
@@ -96,15 +110,18 @@ export class KBoot {
const response = await this.peripheral.sendCommand(command); const response = await this.peripheral.sendCommand(command);
if (response.tag !== ResponseTags.Generic) { if (response.tag !== ResponseTags.Generic) {
logger('Response tag is not generic response: %d', response.tag);
throw new Error('Response tag is not generic response'); throw new Error('Response tag is not generic response');
} }
if (response.code !== ResponseCodes.Success) { 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> { async flashEraseAllUnsecure(): Promise<void> {
logger('Start flash erase all unsecure');
const command: CommandOption = { const command: CommandOption = {
command: Commands.FlashEraseAllUnsecure, command: Commands.FlashEraseAllUnsecure,
params: [] params: []
@@ -113,19 +130,23 @@ export class KBoot {
const response = await this.peripheral.sendCommand(command); const response = await this.peripheral.sendCommand(command);
if (response.tag !== ResponseTags.Generic) { if (response.tag !== ResponseTags.Generic) {
logger('Response tag is not generic response: %d', response.tag);
throw new Error('Response tag is not generic response'); throw new Error('Response tag is not generic response');
} }
if (response.code !== ResponseCodes.Success) { 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> { async readMemory(startAddress: number, count: number): Promise<any> {
logger('Start read memory %o', { startAddress, count });
return this.peripheral.readMemory(startAddress, count); return this.peripheral.readMemory(startAddress, count);
} }
async writeMemory(options: DataOption): Promise<void> { async writeMemory(options: DataOption): Promise<void> {
logger('Start write memory %o', { options });
return this.peripheral.writeMemory(options); return this.peripheral.writeMemory(options);
} }
@@ -133,6 +154,7 @@ export class KBoot {
* Reset the bootloader * Reset the bootloader
*/ */
async reset(): Promise<void> { async reset(): Promise<void> {
logger('Start reset the bootloader');
const command: CommandOption = { const command: CommandOption = {
command: Commands.Reset, command: Commands.Reset,
params: [] params: []
@@ -154,10 +176,12 @@ export class KBoot {
} }
if (response.tag !== ResponseTags.Generic) { if (response.tag !== ResponseTags.Generic) {
logger('Response tag is not generic response: %d', response.tag);
throw new Error('Response tag is not generic response'); throw new Error('Response tag is not generic response');
} }
if (response.code !== ResponseCodes.Success) { if (response.code !== ResponseCodes.Success) {
logger('Unknown error %d', response.code);
throw new Error(`Unknown error. Error code:${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 * @param [speed=64] - Speed of the I2C
*/ */
async configureI2c(address: number, speed = 64): Promise<void> { async configureI2c(address: number, speed = 64): Promise<void> {
logger('Start configure I2C', { address, speed });
if (address > 127) { if (address > 127) {
logger('Only 7-bit i2c address is supported');
throw new Error('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); const response = await this.peripheral.sendCommand(command);
if (response.tag !== ResponseTags.Generic) { if (response.tag !== ResponseTags.Generic) {
logger('Response tag is not generic response: %d', response.tag);
throw new Error('Response tag is not generic response'); throw new Error('Response tag is not generic response');
} }
if (response.code !== ResponseCodes.Success) { if (response.code !== ResponseCodes.Success) {
logger('Unknown error %d', response.code);
throw new Error(`Unknown error. Error code:${response.code}`); throw new Error(`Unknown error. Error code:${response.code}`);
} }
} }

View File

@@ -55,6 +55,8 @@ export class UsbPeripheral implements Peripheral {
close(): void { close(): void {
if (this._device) { if (this._device) {
this._device.close(); this._device.close();
this._device.removeAllListeners('data');
this._device.removeAllListeners('error');
this._device = undefined; this._device = undefined;
} }
} }
@@ -222,6 +224,7 @@ export class UsbPeripheral implements Peripheral {
} }
private _readFromBuffer(bufferName: string, byte: number, timeout: number): Promise<Buffer> { 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) => { return new Promise<Buffer>(async (resolve, reject) => {
const startTime = new Date(); const startTime = new Date();
while (startTime.getTime() + timeout > new Date().getTime()) { while (startTime.getTime() + timeout > new Date().getTime()) {
@@ -266,6 +269,7 @@ export class UsbPeripheral implements Peripheral {
} }
private async _getNextCommandResponse(): Promise<CommandResponse> { private async _getNextCommandResponse(): Promise<CommandResponse> {
logger('Start read next command response');
const response = await this._readFromCommandStream(); const response = await this._readFromCommandStream();
const commandResponse = decodeCommandResponse(response); const commandResponse = decodeCommandResponse(response);
logger('next command response: %o', commandResponse); logger('next command response: %o', commandResponse);

View File

@@ -29,6 +29,7 @@
"scripts": { "scripts": {
"start": "cross-env DEBUG=kboot* electron ./dist/electron-main.js", "start": "cross-env DEBUG=kboot* electron ./dist/electron-main.js",
"electron:spe": "electron ./dist/electron-main.js --spe", "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": "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", "build:usb": "electron-rebuild -w node-hid -p -m ./dist",
"lint": "tslint --project tsconfig.json", "lint": "tslint --project tsconfig.json",

View File

@@ -11,6 +11,7 @@ import * as commandLineArgs from 'command-line-args';
import { UhkHidDevice, UhkOperations } from 'uhk-usb'; import { UhkHidDevice, UhkOperations } from 'uhk-usb';
// import { ElectronDataStorageRepositoryService } from './services/electron-datastorage-repository.service'; // import { ElectronDataStorageRepositoryService } from './services/electron-datastorage-repository.service';
import { CommandLineArgs, LogRegExps } from 'uhk-common'; import { CommandLineArgs, LogRegExps } from 'uhk-common';
import { UhkBlhost } from 'uhk-usb';
import { DeviceService } from './services/device.service'; import { DeviceService } from './services/device.service';
import { logger } from './services/logger.service'; import { logger } from './services/logger.service';
import { AppUpdateService } from './services/app-update.service'; import { AppUpdateService } from './services/app-update.service';
@@ -22,7 +23,8 @@ import { loadWindowState, saveWindowState } from './util/window';
const optionDefinitions = [ const optionDefinitions = [
{name: 'addons', type: Boolean}, {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); const options: CommandLineArgs = commandLineArgs(optionDefinitions);
@@ -36,6 +38,7 @@ let win: Electron.BrowserWindow;
autoUpdater.logger = logger; autoUpdater.logger = logger;
let deviceService: DeviceService; let deviceService: DeviceService;
let uhkBlhost: UhkBlhost;
let uhkHidDeviceService: UhkHidDevice; let uhkHidDeviceService: UhkHidDevice;
let uhkOperations: UhkOperations; let uhkOperations: UhkOperations;
let appUpdateService: AppUpdateService; let appUpdateService: AppUpdateService;
@@ -100,8 +103,9 @@ function createWindow() {
setMenu(win); setMenu(win);
uhkHidDeviceService = new UhkHidDevice(logger, options, packagesDir); uhkHidDeviceService = new UhkHidDevice(logger, options, packagesDir);
uhkOperations = new UhkOperations(logger, uhkHidDeviceService, packagesDir); uhkBlhost = new UhkBlhost(logger, packagesDir);
deviceService = new DeviceService(logger, win, uhkHidDeviceService, uhkOperations, packagesDir); uhkOperations = new UhkOperations(logger, uhkBlhost, uhkHidDeviceService, packagesDir);
deviceService = new DeviceService(logger, win, uhkHidDeviceService, uhkOperations, packagesDir, options);
appUpdateService = new AppUpdateService(logger, win, app); appUpdateService = new AppUpdateService(logger, win, app);
appService = new AppService(logger, win, deviceService, options, uhkHidDeviceService); appService = new AppService(logger, win, deviceService, options, uhkHidDeviceService);
sudoService = new SudoService(logger, options); sudoService = new SudoService(logger, options);

View File

@@ -1,6 +1,7 @@
import { ipcMain } from 'electron'; import { ipcMain } from 'electron';
import { isEqual } from 'lodash'; import { isEqual } from 'lodash';
import { import {
CommandLineArgs,
ConfigurationReply, ConfigurationReply,
DeviceConnectionState, DeviceConnectionState,
FirmwareUpgradeIpcResponse, FirmwareUpgradeIpcResponse,
@@ -41,7 +42,8 @@ export class DeviceService {
private win: Electron.BrowserWindow, private win: Electron.BrowserWindow,
private device: UhkHidDevice, private device: UhkHidDevice,
private operations: UhkOperations, private operations: UhkOperations,
private rootDir: string) { private rootDir: string,
private options: CommandLineArgs) {
this.startPollUhkDevice(); this.startPollUhkDevice();
this.uhkDevicePoller() this.uhkDevicePoller()
.catch(error => { .catch(error => {
@@ -182,15 +184,25 @@ export class DeviceService {
const packageJson = await getPackageJsonFromPathAsync(firmwarePathData.packageJsonPath); const packageJson = await getPackageJsonFromPathAsync(firmwarePathData.packageJsonPath);
this.logService.debug('New firmware version:', packageJson.firmwareVersion); this.logService.debug('New firmware version:', packageJson.firmwareVersion);
await this.operations.updateRightFirmware(firmwarePathData.rightFirmwarePath); if (this.options.useKboot) {
await this.operations.updateLeftModule(firmwarePathData.leftFirmwarePath); 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 { } else {
const packageJsonPath = path.join(this.rootDir, 'packages/firmware/package.json'); const packageJsonPath = path.join(this.rootDir, 'packages/firmware/package.json');
const packageJson = await getPackageJsonFromPathAsync(packageJsonPath); const packageJson = await getPackageJsonFromPathAsync(packageJsonPath);
this.logService.debug('New firmware version:', packageJson.firmwareVersion); this.logService.debug('New firmware version:', packageJson.firmwareVersion);
await this.operations.updateRightFirmware(); if (this.options.useKboot) {
await this.operations.updateLeftModule(); await this.operations.updateRightFirmwareWithKboot();
await this.operations.updateLeftModuleWithKboot();
} else {
await this.operations.updateRightFirmwareWithBlhost();
await this.operations.updateLeftModuleWithBlhost();
}
} }
response.success = true; response.success = true;
@@ -220,7 +232,11 @@ export class DeviceService {
try { try {
await this.stopPollUhkDevice(); 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.modules = await this.getHardwareModules(false);
response.success = true; response.success = true;
@@ -270,8 +286,8 @@ export class DeviceService {
while (true) { while (true) {
if (this._pollerAllowed) { if (this._pollerAllowed) {
this._uhkDevicePolling = true; this._uhkDevicePolling = true;
try {
const state = await this.device.getDeviceConnectionStateAsync(); const state = await this.device.getDeviceConnectionStateAsync();
if (!isEqual(state, savedState)) { if (!isEqual(state, savedState)) {
@@ -279,10 +295,12 @@ export class DeviceService {
this.win.webContents.send(IpcEvents.device.deviceConnectionStateChanged, state); this.win.webContents.send(IpcEvents.device.deviceConnectionStateChanged, state);
this.logService.info('[DeviceService] Device connection state changed to:', state); this.logService.info('[DeviceService] Device connection state changed to:', state);
} }
} catch (err) {
this._uhkDevicePolling = false; this.logService.error('[DeviceService] Device connection state query error', err);
}
} }
this._uhkDevicePolling = false;
await snooze(250); await snooze(250);
} }
} }

View File

@@ -7,4 +7,8 @@ export interface CommandLineArgs {
* simulate privilege escalation error * simulate privilege escalation error
*/ */
spe?: boolean; spe?: boolean;
/**
* If it is true use kboot package instead of blhost for firmware upgrade
*/
useKboot?: boolean;
} }

View File

@@ -71,9 +71,9 @@ export enum EnumerationNameToProductId {
} }
export enum ModuleSlotToI2cAddress { export enum ModuleSlotToI2cAddress {
leftHalf = 0x10, leftHalf = '0x10',
leftModule = 0x20, leftModule = '0x20',
rightModule = 0x30 rightModule = '0x30'
} }
export enum ModuleSlotToId { export enum ModuleSlotToId {

View File

@@ -1,4 +1,5 @@
export * from './constants'; export * from './constants';
export * from './uhk-blhost';
export * from './uhk-hid-device'; export * from './uhk-hid-device';
export * from './uhk-operations'; export * from './uhk-operations';
export * from './util'; export * from './util';

View 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;
}
}

View File

@@ -208,7 +208,7 @@ export class UhkHidDevice {
while (new Date().getTime() - startTime.getTime() < 20000) { while (new Date().getTime() - startTime.getTime() < 20000) {
const devs = devices(); const devs = devices();
this.logService.silly('[UhkHidDevice] reenumeration devices', devs); this.logService.debug('[UhkHidDevice] reenumeration devices', devs);
const inBootloaderMode = devs.some((x: Device) => const inBootloaderMode = devs.some((x: Device) =>
x.vendorId === Constants.VENDOR_ID && x.vendorId === Constants.VENDOR_ID &&
@@ -219,7 +219,7 @@ export class UhkHidDevice {
return; 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); await snooze(100);
if (!jumped) { if (!jumped) {
@@ -232,7 +232,7 @@ export class UhkHidDevice {
device.close(); device.close();
jumped = true; jumped = true;
} else { } 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) { if (command === KbootCommands.idle) {
transfer = Buffer.from([UsbCommand.SendKbootCommandToModule, command]); transfer = Buffer.from([UsbCommand.SendKbootCommandToModule, command]);
} else { } 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); await retry(async () => await this.write(transfer), maxTry, this.logService);
} }

View File

@@ -13,6 +13,7 @@ import {
import * as path from 'path'; import * as path from 'path';
import * as fs from 'fs'; import * as fs from 'fs';
import * as os from 'os'; import * as os from 'os';
import { UhkBlhost } from './uhk-blhost';
import { UhkHidDevice } from './uhk-hid-device'; import { UhkHidDevice } from './uhk-hid-device';
import { readBootloaderFirmwareFromHexFileAsync, snooze, waitForDevice } from './util'; import { readBootloaderFirmwareFromHexFileAsync, snooze, waitForDevice } from './util';
import { ConfigBufferId, convertBufferToIntArray, DevicePropertyIds, getTransferBuffers, UsbCommand } from '../index'; import { ConfigBufferId, convertBufferToIntArray, DevicePropertyIds, getTransferBuffers, UsbCommand } from '../index';
@@ -20,11 +21,67 @@ import { LoadConfigurationsResult } from './models/load-configurations-result';
export class UhkOperations { export class UhkOperations {
constructor(private logService: LogService, constructor(private logService: LogService,
private blhost: UhkBlhost,
private device: UhkHidDevice, private device: UhkHidDevice,
private rootDir: string) { 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] Operating system: ${os.type()} ${os.release()} ${os.arch()}`);
this.logService.debug('[UhkOperations] Start flashing right firmware'); this.logService.debug('[UhkOperations] Start flashing right firmware');
@@ -58,9 +115,10 @@ export class UhkOperations {
this.logService.debug('[UhkOperations] Right firmware successfully flashed'); 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'); this.logService.debug('[UhkOperations] Start flashing left module firmware');
const i2cAddressOfLeftModule = Number.parseInt(ModuleSlotToI2cAddress.leftHalf, 16);
await this.device.reenumerate(EnumerationModes.NormalKeyboard); await this.device.reenumerate(EnumerationModes.NormalKeyboard);
this.device.close(); this.device.close();
await snooze(1000); await snooze(1000);
@@ -86,28 +144,31 @@ export class UhkOperations {
while (true) { while (true) {
try { try {
this.logService.debug('[UhkOperations] Try to connect to the LEFT keyboard'); 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); await kboot.getProperty(Properties.BootloaderVersion);
break; break;
} catch { } catch {
if (tryCount > 100) { if (tryCount > 100) {
throw new Error('Can not connect to the LEFT keyboard'); throw new Error('Can not connect to the LEFT keyboard');
} }
} finally { await snooze(2000);
kboot.close();
} }
await snooze(100);
tryCount++; 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'); this.logService.debug('[UhkOperations] Flash erase all on LEFT keyboard');
await kboot.configureI2c(ModuleSlotToI2cAddress.leftHalf); await kboot.configureI2c(i2cAddressOfLeftModule);
await kboot.flashEraseAllUnsecure(); await kboot.flashEraseAllUnsecure();
this.logService.debug('[UhkOperations] Read LEFT firmware from file'); this.logService.debug('[UhkOperations] Read LEFT firmware from file');
const configData = fs.readFileSync(firmwarePath); const configData = fs.readFileSync(firmwarePath);
this.logService.debug('[UhkOperations] Write memory'); this.logService.debug('[UhkOperations] Write memory');
await kboot.configureI2c(ModuleSlotToI2cAddress.leftHalf); await kboot.configureI2c(i2cAddressOfLeftModule);
await kboot.writeMemory({ startAddress: 0, data: configData }); await kboot.writeMemory({ startAddress: 0, data: configData });
this.logService.debug('[UhkOperations] Reset LEFT keyboard'); this.logService.debug('[UhkOperations] Reset LEFT keyboard');

View File

@@ -7,9 +7,18 @@ const rootDir = path.join(__dirname, '../../tmp');
const uhkHidDevice = new UhkHidDevice(logService, {}, rootDir); const uhkHidDevice = new UhkHidDevice(logService, {}, rootDir);
const uhkOperations = new UhkOperations(logService, uhkHidDevice, 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 uhkOperations
.updateRightFirmware() .updateRightFirmwareWithKboot()
.then(() => uhkOperations.updateLeftModule()) .then(() => uhkOperations.updateLeftModuleWithKboot())
.then(() => console.log('Firmware upgrade finished')) .then(() => console.log('Firmware upgrade finished'))
.catch(error => { .catch(error => {
console.error(error); console.error(error);

View File

@@ -8,6 +8,13 @@ const copyOptions = {
const promises = []; const promises = [];
promises.push(
fse.copy(
path.join(__dirname, '../packages/usb/blhost'),
path.join(__dirname, '../tmp/packages/blhost'),
copyOptions)
);
promises.push( promises.push(
fse.copy( fse.copy(
path.join(__dirname, '../rules'), path.join(__dirname, '../rules'),

View File

@@ -96,7 +96,7 @@ if (TEST_BUILD || gitTag) {
const rootJson = require('../package.json'); const rootJson = require('../package.json');
update2ndPackageJson(rootJson); update2ndPackageJson(rootJson);
// Add firmware to extra resources // Add firmware and blhost to extra resources
const extractedFirmwareDir = path.join(__dirname, '../tmp/packages'); const extractedFirmwareDir = path.join(__dirname, '../tmp/packages');
extraResources.push({from: extractedFirmwareDir, to: 'packages/'}); extraResources.push({from: extractedFirmwareDir, to: 'packages/'});