feat(config): Read / write hardware configuration area (#423)

* add write-hca.js

* refactor: Move config serializer into the uhk-common package

* refactor: Move getTransferBuffers into the uhk-usb package

* refactor: delete obsoleted classes

* build: add uhk-usb build command

* refactor: move eeprom transfer to uhk-usb package

* fix: Fix write-hca.js

* feat: load hardware config from the device and

* style: fix ts lint errors

* build: fix rxjs dependency resolve

* test: Add jasmine unit test framework to the tet serializer

* fix(user-config): A "type": "basic", properties to the "keystroke" action types

* feat(usb): set chmod+x on write-hca.js

* feat(usb): Create USB logger

* style: Fix type

* build: Add chalk to dependencies.

Chalk will colorize the output
This commit is contained in:
Róbert Kiss
2017-09-26 18:57:27 +02:00
committed by László Monda
parent 1122784bdb
commit 9294bede50
130 changed files with 9108 additions and 1991 deletions

View File

@@ -1,10 +1,10 @@
import { ipcMain } from 'electron';
import { Constants, IpcEvents, LogService, IpcResponse } from 'uhk-common';
import { IpcEvents, LogService, IpcResponse, ConfigurationReply } from 'uhk-common';
import { Constants, EepromTransfer, SystemPropertyIds, UsbCommand } from 'uhk-usb';
import { Observable } from 'rxjs/Observable';
import { Subscription } from 'rxjs/Subscription';
import { Device, devices } from 'node-hid';
import { UhkHidDevice } from 'uhk-usb';
import 'rxjs/add/observable/interval';
import 'rxjs/add/operator/startWith';
@@ -12,38 +12,6 @@ import 'rxjs/add/operator/map';
import 'rxjs/add/operator/do';
import 'rxjs/add/operator/distinctUntilChanged';
import { UhkHidDeviceService } from './uhk-hid-device.service';
/**
* UHK USB Communications command. All communication package should have start with a command code.
*/
enum Command {
GetProperty = 0,
UploadConfig = 8,
ApplyConfig = 9,
LaunchEepromTransfer = 12,
ReadUserConfig = 15,
GetKeyboardState = 16
}
enum EepromTransfer {
ReadHardwareConfig = 0,
WriteHardwareConfig = 1,
ReadUserConfig = 2,
WriteUserConfig = 3
}
enum SystemPropertyIds {
UsbProtocolVersion = 0,
BridgeProtocolVersion = 1,
DataModelVersion = 2,
FirmwareVersion = 3,
HardwareConfigSize = 4,
UserConfigSize = 5
}
const snooze = ms => new Promise(resolve => setTimeout(resolve, ms));
/**
* IpcMain pair of the UHK Communication
* Functionality:
@@ -57,10 +25,10 @@ export class DeviceService {
constructor(private logService: LogService,
private win: Electron.BrowserWindow,
private device: UhkHidDeviceService) {
private device: UhkHidDevice) {
this.pollUhkDevice();
ipcMain.on(IpcEvents.device.saveUserConfiguration, this.saveUserConfiguration.bind(this));
ipcMain.on(IpcEvents.device.loadUserConfiguration, this.loadUserConfiguration.bind(this));
ipcMain.on(IpcEvents.device.loadConfigurations, this.loadConfigurations.bind(this));
logService.debug('[DeviceService] init success');
}
@@ -76,32 +44,64 @@ export class DeviceService {
* Return with the actual UserConfiguration from UHK Device
* @returns {Promise<Buffer>}
*/
public async loadUserConfiguration(event: Electron.Event): Promise<void> {
public async loadConfigurations(event: Electron.Event): Promise<void> {
try {
const userConfiguration = await this.loadConfiguration(
SystemPropertyIds.UserConfigSize,
UsbCommand.ReadUserConfig,
'user configuration');
const hardwareConfiguration = await this.loadConfiguration(
SystemPropertyIds.HardwareConfigSize,
UsbCommand.ReadHardwareConfig,
'hardware configuration');
const response: ConfigurationReply = {
success: true,
userConfiguration,
hardwareConfiguration
};
event.sender.send(IpcEvents.device.loadConfigurationReply, JSON.stringify(response));
} catch (error) {
const response: ConfigurationReply = {
success: false,
error: error.message
};
event.sender.send(IpcEvents.device.loadConfigurationReply, JSON.stringify(response));
} finally {
this.device.close();
}
}
/**
* Return with the actual user / hardware fonfiguration from UHK Device
* @returns {Promise<Buffer>}
*/
public async loadConfiguration(property: SystemPropertyIds, config: UsbCommand, configName: string): Promise<string> {
let response = [];
try {
this.logService.debug('[DeviceService] USB[T]: Read user configuration size from keyboard');
const configSize = await this.getUserConfigSizeFromKeyboard();
this.logService.debug(`[DeviceService] USB[T]: Read ${configName} size from keyboard`);
const configSize = await this.getConfigSizeFromKeyboard(property);
const chunkSize = 63;
let offset = 0;
let configBuffer = new Buffer(0);
this.logService.debug('[DeviceService] USB[T]: Read user configuration from keyboard');
this.logService.debug(`[DeviceService] USB[T]: Read ${configName} from keyboard`);
while (offset < configSize) {
const chunkSizeToRead = Math.min(chunkSize, configSize - offset);
const writeBuffer = Buffer.from([Command.ReadUserConfig, chunkSizeToRead, offset & 0xff, offset >> 8]);
const writeBuffer = Buffer.from([config, chunkSizeToRead, offset & 0xff, offset >> 8]);
const readBuffer = await this.device.write(writeBuffer);
configBuffer = Buffer.concat([configBuffer, new Buffer(readBuffer.slice(1, chunkSizeToRead + 1))]);
offset += chunkSizeToRead;
}
response = UhkHidDeviceService.convertBufferToIntArray(configBuffer);
response = UhkHidDevice.convertBufferToIntArray(configBuffer);
return Promise.resolve(JSON.stringify(response));
} catch (error) {
this.logService.error('[DeviceService] getUserConfigFromEeprom error', error);
} finally {
this.device.close();
const errMsg = `[DeviceService] ${configName} from eeprom error`;
this.logService.error(errMsg, error);
throw new Error(errMsg);
}
event.sender.send(IpcEvents.device.loadUserConfigurationReply, JSON.stringify(response));
}
/**
@@ -127,11 +127,11 @@ export class DeviceService {
}
/**
* Return the UserConfiguration size from the UHK Device
* Return the user / hardware configuration size from the UHK Device
* @returns {Promise<number>}
*/
private async getUserConfigSizeFromKeyboard(): Promise<number> {
const buffer = await this.device.write(new Buffer([Command.GetProperty, SystemPropertyIds.UserConfigSize]));
private async getConfigSizeFromKeyboard(property: SystemPropertyIds): Promise<number> {
const buffer = await this.device.write(new Buffer([UsbCommand.GetProperty, property]));
const configSize = buffer[1] + (buffer[2] << 8);
this.logService.debug('[DeviceService] User config size:', configSize);
return configSize;
@@ -144,7 +144,7 @@ export class DeviceService {
this.logService.debug('[DeviceService] USB[T]: Write user configuration to keyboard');
await this.sendUserConfigToKeyboard(json);
this.logService.debug('[DeviceService] USB[T]: Write user configuration to EEPROM');
await this.writeUserConfigToEeprom();
await this.device.writeConfigToEeprom(EepromTransfer.WriteUserConfig);
response.success = true;
}
@@ -168,48 +168,12 @@ export class DeviceService {
*/
private async sendUserConfigToKeyboard(json: string): Promise<void> {
const buffer: Buffer = new Buffer(JSON.parse(json).data);
const fragments = this.getTransferBuffers(buffer);
const fragments = UhkHidDevice.getTransferBuffers(UsbCommand.UploadUserConfig, buffer);
for (const fragment of fragments) {
await this.device.write(fragment);
}
this.logService.debug('[DeviceService] USB[T]: Apply user configuration to keyboard');
const applyBuffer = new Buffer([Command.ApplyConfig]);
const applyBuffer = new Buffer([UsbCommand.ApplyConfig]);
await this.device.write(applyBuffer);
}
private async writeUserConfigToEeprom(): Promise<void> {
await this.device.write(new Buffer([Command.LaunchEepromTransfer, EepromTransfer.WriteUserConfig]));
await this.waitUntilKeyboardBusy();
}
private async waitUntilKeyboardBusy(): Promise<void> {
while (true) {
const buffer = await this.device.write(new Buffer([Command.GetKeyboardState]));
if (buffer[1] === 0) {
break;
}
this.logService.debug('Keyboard is busy, wait...');
await snooze(200);
}
}
/**
* Split the whole UserConfiguration package into 64 byte fragments
* @param {Buffer} configBuffer
* @returns {Buffer[]}
* @private
*/
private getTransferBuffers(configBuffer: Buffer): Buffer[] {
const fragments: Buffer[] = [];
const MAX_SENDING_PAYLOAD_SIZE = Constants.MAX_PAYLOAD_SIZE - 4;
for (let offset = 0; offset < configBuffer.length; offset += MAX_SENDING_PAYLOAD_SIZE) {
const length = offset + MAX_SENDING_PAYLOAD_SIZE < configBuffer.length
? MAX_SENDING_PAYLOAD_SIZE
: configBuffer.length - offset;
const header = new Buffer([Command.UploadConfig, length, offset & 0xFF, offset >> 8]);
fragments.push(Buffer.concat([header, configBuffer.slice(offset, offset + length)]));
}
return fragments;
}
}