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,6 +1,6 @@
import * as storage from 'electron-settings';
import { UserConfiguration } from '../../app/config-serializer/config-items/user-configuration';
import { UserConfiguration } from 'uhk-common';
import { DataStorageRepositoryService } from '../../app/services/datastorage-repository.service';
import { AutoUpdateSettings } from '../../app/models/auto-update-settings';

View File

@@ -1,25 +1,19 @@
import { Injectable } from '@angular/core';
import * as log from 'electron-log';
import * as util from 'util';
import { LogService } from 'uhk-common';
const transferRegExp = /USB\[T]:/;
const writeRegExp = /USB\[W]:/;
const readRegExp = /USB\[R]: 00/;
const errorRegExp = /(?:(USB\[R]: ([^0]|0[^0])))/;
import { LogService, LogRegExps } from 'uhk-common';
// https://github.com/megahertz/electron-log/issues/44
// console.debug starting with Chromium 58 this method is a no-op on Chromium browsers.
if (console.debug) {
console.debug = (...args: any[]): void => {
if (writeRegExp.test(args[0])) {
if (LogRegExps.writeRegExp.test(args[0])) {
console.log('%c' + args[0], 'color:blue');
} else if (readRegExp.test(args[0])) {
} else if (LogRegExps.readRegExp.test(args[0])) {
console.log('%c' + args[0], 'color:green');
} else if (errorRegExp.test(args[0])) {
} else if (LogRegExps.errorRegExp.test(args[0])) {
console.log('%c' + args[0], 'color:red');
}else if (transferRegExp.test(args[0])) {
}else if (LogRegExps.transferRegExp.test(args[0])) {
console.log('%c' + args[0], 'color:orange');
} else {
console.log(...args);

View File

@@ -1,17 +0,0 @@
import { LogService } from 'uhk-common';
import { UhkDeviceService } from './uhk-device.service';
import { UhkHidApiService } from './uhk-hid-api.service';
export function uhkDeviceFactory(logService: LogService): UhkDeviceService {
// HID API officially support MAC, WIN and linux x64 platform
// https://github.com/node-hid/node-hid#platform-support
if (process.platform === 'darwin' ||
process.platform === 'win32' ||
(process.platform === 'linux' && process.arch === 'x64')) {
return new UhkHidApiService(logService);
}
// On other platform use libUsb, but we try to test on all platform
// return new UhkLibUsbApiService(logService);
return new UhkHidApiService(logService);
}

View File

@@ -1,106 +0,0 @@
import { OnDestroy } from '@angular/core';
import { Observable } from 'rxjs/Observable';
import { Subject } from 'rxjs/Subject';
import { Observer } from 'rxjs/Observer';
import { Subscriber } from 'rxjs/Subscriber';
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
import { Subscription } from 'rxjs/Subscription';
import { Constants, LogService } from 'uhk-common';
import { SenderMessage } from '../models/sender-message';
enum Command {
UploadConfig = 8,
ApplyConfig = 9
}
export abstract class UhkDeviceService implements OnDestroy {
protected connected$: BehaviorSubject<boolean>;
protected initialized$: BehaviorSubject<boolean>;
protected deviceOpened$: BehaviorSubject<boolean>;
protected outSubscription: Subscription;
protected messageIn$: Observable<Buffer>;
protected messageOut$: Subject<SenderMessage>;
constructor(protected logService: LogService) {
this.messageOut$ = new Subject<SenderMessage>();
this.initialized$ = new BehaviorSubject(false);
this.connected$ = new BehaviorSubject(false);
this.deviceOpened$ = new BehaviorSubject(false);
this.outSubscription = Subscription.EMPTY;
}
ngOnDestroy() {
this.disconnect();
this.initialized$.unsubscribe();
this.connected$.unsubscribe();
this.deviceOpened$.unsubscribe();
}
sendConfig(configBuffer: Buffer): Observable<Buffer> {
return Observable.create((subscriber: Subscriber<Buffer>) => {
this.logService.info('Sending...', configBuffer);
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)]));
}
const buffers: Buffer[] = [];
const observer: Observer<Buffer> = {
next: (buffer: Buffer) => buffers.push(buffer),
error: error => subscriber.error(error),
complete: () => {
if (buffers.length === fragments.length) {
subscriber.next(Buffer.concat(buffers));
subscriber.complete();
this.logService.info('Sending finished');
}
}
};
fragments
.map<SenderMessage>(fragment => ({ buffer: fragment, observer }))
.forEach(senderPackage => this.messageOut$.next(senderPackage));
});
}
applyConfig(): Observable<Buffer> {
return Observable.create((subscriber: Subscriber<Buffer>) => {
this.logService.info('Applying configuration');
this.messageOut$.next({
buffer: new Buffer([Command.ApplyConfig]),
observer: subscriber
});
});
}
isInitialized(): Observable<boolean> {
return this.initialized$.asObservable();
}
isConnected(): Observable<boolean> {
return this.connected$.asObservable();
}
isOpened(): Observable<boolean> {
return this.deviceOpened$.asObservable();
}
disconnect() {
this.outSubscription.unsubscribe();
this.messageIn$ = undefined;
this.initialized$.next(false);
this.deviceOpened$.next(false);
this.connected$.next(false);
}
abstract initialize(): void;
abstract hasPermissions(): Observable<boolean>;
}

View File

@@ -1,136 +0,0 @@
import { Injectable, OnDestroy } from '@angular/core';
import { Observable } from 'rxjs/Observable';
import { Subscriber } from 'rxjs/Subscriber';
import { Device, devices, HID } from 'node-hid';
import 'rxjs/add/observable/empty';
import 'rxjs/add/observable/interval';
import 'rxjs/add/operator/catch';
import 'rxjs/add/operator/concat';
import 'rxjs/add/operator/combineLatest';
import 'rxjs/add/operator/concatMap';
import 'rxjs/add/operator/publish';
import 'rxjs/add/operator/do';
import 'rxjs/add/operator/startWith';
import 'rxjs/add/operator/map';
import { Constants, LogService } from 'uhk-common';
import { UhkDeviceService } from './uhk-device.service';
@Injectable()
export class UhkHidApiService extends UhkDeviceService implements OnDestroy {
private device: HID;
constructor(protected logService: LogService) {
super(logService);
}
ngOnDestroy() {
super.ngOnDestroy();
}
initialize(): void {
if (this.initialized$.getValue()) {
return;
}
this.device = this.getDevice();
if (!this.device) {
return;
}
this.deviceOpened$.next(true);
this.messageIn$ = Observable.create((subscriber: Subscriber<Buffer>) => {
this.logService.info('Try to read');
this.device.read((error: any, data: any = []) => {
if (error) {
this.logService.error('reading error', error);
subscriber.error(error);
} else {
this.logService.info('read data', data);
subscriber.next(data);
subscriber.complete();
}
});
});
const outSending = this.messageOut$.concatMap(senderPackage => {
return (<Observable<void>>Observable.create((subscriber: Subscriber<void>) => {
this.logService.info('transfering', senderPackage.buffer);
const data = Array.prototype.slice.call(senderPackage.buffer, 0);
// if data start with 0 need to add additional leading zero because HID API remove it.
// https://github.com/node-hid/node-hid/issues/187
if (data.length > 0 && data[0] === 0) {
data.unshift(0);
}
// From HID API documentation:
// http://www.signal11.us/oss/hidapi/hidapi/doxygen/html/group__API.html#gad14ea48e440cf5066df87cc6488493af
// The first byte of data[] must contain the Report ID.
// For devices which only support a single report, this must be set to 0x0.
data.unshift(0);
try {
this.device.write(data);
this.logService.info('transfering finished');
subscriber.complete();
}
catch (error) {
this.logService.error('transfering errored', error);
subscriber.error(error);
}
})).concat(this.messageIn$)
.do(buffer => senderPackage.observer.next(buffer) && senderPackage.observer.complete())
.catch((error: string) => {
senderPackage.observer.error(error);
return Observable.empty<void>();
});
}).publish();
this.outSubscription = outSending.connect();
this.initialized$.next(true);
}
hasPermissions(): Observable<boolean> {
return this.isConnected()
.combineLatest(this.deviceOpened$)
.map((latest: boolean[]) => {
const connected = latest[0];
const opened = latest[1];
if (!connected) {
return false;
} else if (opened) {
return true;
}
return true;
});
}
/**
* Return the 0 interface of the keyboard.
* @returns {HID}
*/
getDevice(): HID {
try {
const devs = devices();
this.logService.info('Available devices:', devs);
const dev = devs.find((x: Device) =>
x.vendorId === Constants.VENDOR_ID &&
x.productId === Constants.PRODUCT_ID &&
((x.usagePage === 128 && x.usage === 129) || x.interface === 0));
const device = new HID(dev.path);
this.logService.info('Used device:', dev);
return device;
}
catch (err) {
this.logService.error('Can not create device:', err);
}
return null;
}
}