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
|
"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": {
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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';
|
||||||
|
|||||||
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) {
|
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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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');
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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'),
|
||||||
|
|||||||
@@ -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/'});
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user